From 1bd3401f25a8f7e289314686e4bafd2ce416724e Mon Sep 17 00:00:00 2001 From: Ambika Date: Tue, 17 Mar 2026 22:40:45 +0530 Subject: [PATCH] FINERACT-2494: Standardize operationId for Savings module and resolve Checkstyle violations --- .asf.yaml | 3 + .github/workflows/build-cucumber.yml | 14 +- .github/workflows/build-docker.yml | 6 +- .github/workflows/build-documentation.yml | 10 +- .github/workflows/build-e2e-tests.yml | 12 +- .github/workflows/build-mariadb.yml | 21 +- .github/workflows/build-mysql.yml | 12 +- .github/workflows/build-postgresql.yml | 14 +- .../workflows/liquibase-only-postgresql.yml | 10 +- .../mifos-fineract-client-publish.yml | 8 +- .../pr-one-commit-per-user-check.yml | 57 + .github/workflows/publish-dockerhub.yml | 6 +- ...tegration-test-sequentially-postgresql.yml | 12 +- .github/workflows/smoke-messaging.yml | 6 +- .github/workflows/sonarqube.yml | 6 +- .github/workflows/stale.yml | 2 +- .../verify-api-backward-compatibility.yml | 243 + .github/workflows/verify-commits.yml | 4 +- ...erify-liquibase-backward-compatibility.yml | 8 +- README.md | 6 +- build.gradle | 5 +- .../org.apache.fineract.dependencies.gradle | 10 +- config/docker/compose/mariadb.yml | 3 +- config/docker/compose/postgresql.yml | 2 +- .../docker/mariadb/conf.d/mariadb_compat.cnf | 17 + docker-compose-mariadb.yml | 3 +- docker-compose-mysql.yml | 34 + .../api/GLClosuresApiResourceSwagger.java | 20 +- .../FinancialActivityAccountsApiResource.java | 6 +- .../glaccount/api/GLAccountsApiResource.java | 17 +- ...sioningEntriesReadPlatformServiceImpl.java | 57 +- .../main/avro/loan/v1/LoanChargeDataV1.avsc | 12 + .../client/feign/FineractFeignClient.java | 10 + .../fineract/client/util/FineractClient.java | 6 + .../client/test/FineractClientDemo.java | 2 +- .../org/apache/fineract/cob/COBConstant.java | 4 + .../cob/common/CommonPartitioner.java | 101 + .../cob/converter/COBParameterConverter.java | 1 + .../cob/data/BusinessStepNameAndOrder.java | 10 + .../fineract/cob/data/LoanCOBParameter.java | 1 + .../cob/domain/AbstractLockingService.java | 91 + .../fineract/cob/domain/AccountLock.java | 110 + .../cob/domain/AccountLockRepository.java | 49 + .../CustomLoanAccountLockRepository.java | 2 +- .../apache/fineract/cob/domain/LockOwner.java | 0 .../fineract/cob/domain/LockingService.java | 12 +- .../LockCannotBeAppliedException.java | 4 +- .../cob/exceptions/LockedReadException.java | 4 +- .../listener/AbstractLoanItemListener.java | 23 +- .../cob/processor/AbstractItemProcessor.java | 75 + .../cob/resolver/BusinessDateResolver.java | 4 +- .../cob/resolver/CatchUpFlagResolver.java | 4 +- .../service/AbstractAccountLockService.java | 21 +- .../cob/service/AccountLockService.java | 33 + .../BeforeStepLockingItemReaderHelper.java | 69 + .../cob/service/RetrieveIdService.java | 10 +- .../cob/tasklet/ApplyCommonLockTasklet.java | 119 + .../module/fineract-cob/persistence.xml | 2 +- .../CommandPersistenceConfiguration.java | 2 +- .../fineract/command/CommandBaseTest.java | 8 +- .../command/CommandStrategyProvider.java | 2 + .../service/CommandWrapperBuilder.java | 126 +- ...CommandSourceWritePlatformServiceImpl.java | 8 + .../api/GlobalConfigurationConstants.java | 3 + .../domain/ConfigurationDomainService.java | 6 + .../serialization/ThrowableSerialization.java | 0 .../dataqueries/service/CleanupService.java | 27 + .../infrastructure/jobs/service/JobName.java | 1 + .../springbatch/PropertyService.java | 0 .../workingdays/domain/WorkingDays.java | 60 +- .../domain/WorkingDaysEnumerations.java | 4 +- .../account/PortfolioAccountType.java | 10 - .../fineract/portfolio/fund/domain/Fund.java | 2 + .../portfolio/fund/domain/FundRepository.java | 0 .../fund/exception/FundNotFoundException.java | 0 .../fund/service/FundReadPlatformService.java | 0 .../api/PaymentTypeApiResource.java | 2 +- .../savings/domain/SavingsHelper.java | 3 + .../savings/service/SavingsEnumerations.java | 9 + .../useradministration/domain/AppUser.java | 11 + .../api-backward-compatibility.adoc | 190 + .../docs/en/chapters/architecture/index.adoc | 2 + .../src/docs/en/chapters/features/index.adoc | 1 + .../features/loan-origination-details.adoc | 494 + .../docs/en/chapters/testing/cucumber.adoc | 13 +- .../docs/en/chapters/testing/integration.adoc | 2 +- .../api/ImagesApiResource.java | 3 +- .../exception/DocumentNotFoundException.java | 4 +- .../service/ImageReadPlatformServiceImpl.java | 31 +- .../ImageWritePlatformServiceImpl.java | 18 +- .../ImageReadPlatformServiceImplTest.java | 73 + .../ImageWritePlatformServiceImplTest.java | 115 + fineract-e2e-tests-core/build.gradle | 4 + .../config/TestDatabaseConfiguration.java | 68 + .../fineract/test/data/ChargeProductType.java | 3 +- .../apache/fineract/test/data/LoanStatus.java | 2 + .../data/codevalue/CodeValueResolver.java | 2 +- .../fineract/test/data/job/DefaultJob.java | 3 +- .../data/loanproduct/DefaultLoanProduct.java | 2 + .../DefaultWorkingCapitalLoanProduct.java | 30 + .../WorkingCapitalLoanProduct.java | 24 + .../factory/LoanProductsRequestFactory.java | 5 + .../test/factory/LoanRequestFactory.java | 2 +- .../factory/WorkingCapitalRequestFactory.java | 159 + .../test/helper/ErrorMessageHelper.java | 46 + .../helper/GlobalConfigurationHelper.java | 4 +- .../apache/fineract/test/helper/Utils.java | 4 + .../test/helper/WorkFlowJobHelper.java | 42 + .../helper/WorkingCapitalLoanTestHelper.java | 98 + .../BaseFineractInitializerConfiguration.java | 3 +- .../messaging/event/EventCheckHelper.java | 2 +- .../fineract/test/service/JobService.java | 2 +- .../AssetExternalizationStepDef.java | 8 +- .../test/stepdef/common/BatchApiStepDef.java | 12 +- .../BusinessStepConfigurationStepDef.java | 131 + .../test/stepdef/common/ClientStepDef.java | 38 +- .../test/stepdef/common/CurrencyStepDef.java | 138 + .../common/GlobalConfigurationStepDef.java | 2 +- .../stepdef/common/JournalEntriesStepDef.java | 10 +- .../test/stepdef/common/OfficeStepDef.java | 50 + .../test/stepdef/common/SchedulerStepDef.java | 35 + .../test/stepdef/common/UserStepDef.java | 2 +- .../common/WorkingCapitalLoanCobStepDef.java | 259 + .../loan/LoanChargeAdjustmentStepDef.java | 7 +- .../test/stepdef/loan/LoanChargeStepDef.java | 22 +- .../stepdef/loan/LoanDelinquencyStepDef.java | 4 +- .../stepdef/loan/LoanOriginationStepDef.java | 33 +- .../test/stepdef/loan/LoanReAgingStepDef.java | 19 +- .../loan/LoanReAmortizationStepDef.java | 8 +- .../stepdef/loan/LoanRepaymentStepDef.java | 14 +- .../stepdef/loan/LoanRescheduleStepDef.java | 11 + .../test/stepdef/loan/LoanStepDef.java | 147 +- .../stepdef/loan/WorkingCapitalStepDef.java | 808 ++ .../stepdef/reporting/ReportingStepDef.java | 136 + .../stepdef/saving/SavingsAccountStepDef.java | 20 +- .../fineract/test/support/TestContextKey.java | 13 + .../fineract-test-application.properties | 7 + fineract-e2e-tests-runner/build.gradle | 6 +- .../global/ChargeGlobalInitializerStep.java | 7 + .../global/CodeGlobalInitializerStep.java | 5 +- ...lActivityMappingGlobalInitializerStep.java | 3 +- .../global/GLGlobalInitializerStep.java | 2 +- .../LoanProductGlobalInitializerStep.java | 56 +- .../WcpCobBusinessStepInitializerStep.java | 50 + .../global/WorkingCapitalInitializerStep.java | 99 + .../suite/JobSuiteInitializerStep.java | 2 +- .../test/resources/features/Currency.feature | 17 + .../resources/features/EMICalculation.feature | 4 +- .../resources/features/Loan-Part1.feature | 931 ++ .../resources/features/Loan-Part2.feature | 2581 +++++ .../resources/features/Loan-Part3.feature | 2919 ++++++ .../resources/features/Loan-Part4.feature | 2845 ++++++ .../src/test/resources/features/Loan.feature | 9096 ----------------- .../resources/features/LoanCharge.feature | 60 + .../features/LoanContractTermination.feature | 71 + .../features/LoanInterestRateChange.feature | 553 + .../resources/features/LoanReAging.feature | 51 +- .../LoanReAgingEqualAmortization.feature | 352 +- .../features/LoanReAgingPreview.feature | 340 + .../features/LoanReAmortization.feature | 74 +- .../LoanReAmortizationAccruals.feature | 1928 +++- .../resources/features/LoanRepayment.feature | 174 + .../test/resources/features/Reporting.feature | 279 + .../WorkingCapitalLoanProduct.feature | 135 + .../features/WorkingCapital_COB.feature | 218 + .../api/ExternalAssetOwnersApiResource.java | 2 + .../api/LoanOriginatorApiResource.java | 10 +- .../LoanChargeDataV1OriginatorEnricher.java | 72 + .../cob/loan/ContextAwareTaskDecorator.java | 0 .../cob/service/RetrieveLoanIdService.java | 23 + .../loanaccount/domain/LoanCharge.java | 12 + .../LoanRepaymentScheduleInstallment.java | 10 + .../guarantor/domain/GuarantorType.java | 7 +- .../loanschedule/domain/AprCalculator.java | 28 +- .../domain/LoanApplicationTerms.java | 276 +- .../data/LoanRescheduleRequestData.java | 72 +- .../LoanRescheduleRequestStatusEnumData.java | 16 +- .../LoanRescheduleRequestTimelineData.java | 2 + .../LoanRescheduleModelRepaymentPeriod.java | 2 + .../domain/LoanRescheduleRequest.java | 90 +- .../service/LoanBalanceService.java | 27 + .../api/LoanProductsDetailsApiResource.java | 62 + .../data/LoanConfigurationDetails.java | 7 +- .../data/LoanProductBasicDetailsData.java | 36 + .../domain/ILoanConfigurationDetails.java | 3 + .../domain/LoanProductRepository.java | 4 + .../mapper/LoanProductBasicDetailsMapper.java | 45 + .../LoanProductReadBasicDetailsService.java | 28 + ...oanProductReadBasicDetailsServiceImpl.java | 43 + .../LoanProductReadPlatformService.java | 1 + .../domain/AprCalculatorTest.java | 334 + fineract-mix/build.gradle | 75 + fineract-mix/dependencies.gradle | 70 + .../mix/api/MixReportApiResource.java | 15 +- .../mix/api/MixTaxonomyApiResource.java | 10 +- .../api/MixTaxonomyMappingApiResource.java | 40 +- .../MixTaxonomyMappingUpdateCommand.java | 28 + .../mix/data/MixReportXBRLContextData.java | 42 + .../fineract/mix/data/MixReportXBRLData.java | 15 +- .../mix/data/MixReportXBRLNamespaceData.java | 13 +- .../fineract/mix/data/MixTaxonomyData.java | 16 +- .../mix/data/MixTaxonomyMappingData.java | 7 +- .../data/MixTaxonomyMappingUpdateRequest.java | 41 + .../MixTaxonomyMappingUpdateResponse.java | 7 +- .../mix/domain/MixReportXBRLNamespace.java | 48 + .../MixReportXBRLNamespaceRepository.java | 29 + .../fineract/mix/domain/MixTaxonomy.java | 63 + .../mix/domain/MixTaxonomyMapping.java | 53 + .../domain/MixTaxonomyMappingRepository.java | 8 +- .../mix/domain/MixTaxonomyRepository.java | 28 + .../MixReportXBRLMappingInvalidException.java | 4 +- ...ixTaxonomyMappingUpdateCommandHandler.java | 51 + .../mapping/MixReportXBRLNamespaceMapper.java | 30 + .../mix/mapping/MixTaxonomyMapper.java | 32 + .../mix/mapping/MixTaxonomyMappingMapper.java | 30 + ...MixTaxonomyMappingUpdateRequestMapper.java | 30 + .../mix/service/MixReportXBRLBuilder.java | 47 +- .../MixReportXBRLNamespaceReadService.java | 8 +- ...MixReportXBRLNamespaceReadServiceImpl.java | 40 + .../service/MixReportXBRLResultService.java | 6 +- .../MixReportXBRLResultServiceImpl.java | 94 +- .../MixTaxonomyMappingReadService.java | 2 +- .../MixTaxonomyMappingReadServiceImpl.java | 40 + .../MixTaxonomyMappingWriteService.java | 8 +- .../MixTaxonomyMappingWriteServiceImpl.java | 48 + .../mix/service/MixTaxonomyReadService.java | 2 +- .../service/MixTaxonomyReadServiceImpl.java | 46 + ...edPaymentScheduleTransactionProcessor.java | 117 +- .../LoanConfigurationDetailsMapper.java | 2 +- .../calc/ProgressiveEMICalculator.java | 100 +- .../calc/data/OverdueBalanceCorrection.java | 36 + .../ProgressiveLoanInterestScheduleModel.java | 47 +- .../calc/data/RepaymentPeriod.java | 28 +- fineract-provider/build.gradle | 32 +- fineract-provider/dependencies.gradle | 3 +- .../api/JournalEntriesApiResource.java | 2 +- .../JournalEntryReadPlatformServiceImpl.java | 4 +- ...teSavingsAccountChargeCommandStrategy.java | 71 + .../cob/api/COBCatchUpExecutorHelper.java | 42 + .../cob/api/InternalCOBApiResource.java | 6 +- .../cob/api/LoanCOBCatchUpApiResource.java | 24 +- ...rkingCapitalLoanCOBCatchUpApiResource.java | 80 + .../CustomLoanAccountLockRepositoryImpl.java | 3 +- .../fineract/cob/domain/LoanAccountLock.java | 49 +- .../cob/domain/LoanAccountLockRepository.java | 23 +- .../ChunkProcessingLoanItemListener.java | 13 +- .../listener/InlineCOBLoanItemListener.java | 10 +- ...apitalChunkProcessingLoanItemListener.java | 46 + .../cob/loan/AbstractLoanItemProcessor.java | 61 +- .../cob/loan/AbstractLoanItemReader.java | 14 +- .../cob/loan/AbstractLoanItemWriter.java | 4 +- .../cob/loan/ApplyLoanLockTasklet.java | 92 +- .../cob/loan/InlineCOBLoanItemReader.java | 5 +- .../cob/loan/InlineCOBLoanItemWriter.java | 4 +- ...neLoanCOBBuildExecutionContextTasklet.java | 33 +- .../fineract/cob/loan/LoanCOBConstant.java | 3 - .../cob/loan/LoanCOBManagerConfiguration.java | 10 +- .../fineract/cob/loan/LoanCOBPartitioner.java | 81 +- .../cob/loan/LoanCOBWorkerConfiguration.java | 12 +- .../cob/loan/LoanInlineCOBConfig.java | 12 +- .../fineract/cob/loan/LoanItemReader.java | 48 +- .../fineract/cob/loan/LoanItemWriter.java | 4 +- .../cob/loan/LoanLockingConfiguration.java | 4 +- .../cob/loan/LoanLockingServiceImpl.java | 67 +- ...=> RetrieveAllNonClosedIdServiceImpl.java} | 12 +- .../cob/loan/RetrieveLoanIdConfiguration.java | 3 +- .../cob/loan/StayedLockedLoansTasklet.java | 5 +- ...WorkingCapitalInlineCOBLoanItemReader.java | 44 + .../WorkingCapitalLoanInlineCOBConfig.java | 147 + .../cob/service/AsyncCOBExecutorService.java | 26 + .../AsyncCommonCOBExecutorService.java | 111 + .../service/AsyncLoanCOBExecutorService.java | 7 +- .../AsyncLoanCOBExecutorServiceImpl.java | 81 +- ...cWorkingCapitalLoanCOBExecutorService.java | 21 + ...kingCapitalLoanCOBExecutorServiceImpl.java | 61 + .../cob/service/COBCatchUpService.java | 33 + .../cob/service/CommonCOBCatchUpService.java | 76 + ...nlineCommonLockableCOBExecutorService.java | 256 + .../InlineLoanCOBExecutorServiceImpl.java | 224 +- ...kingCapitalLoanCOBExecutorServiceImpl.java | 55 + .../cob/service/LoanAccountLockService.java | 18 +- .../cob/service/LoanCOBCatchUpService.java | 15 +- .../service/LoanCOBCatchUpServiceImpl.java | 51 +- .../WorkingCapitalLoanCOBCatchUpService.java | 22 + ...rkingCapitalLoanCOBCatchUpServiceImpl.java | 45 + ...ntNumberFormatReadPlatformServiceImpl.java | 16 +- .../TemplatePopulateImportConstants.java | 1 + .../guarantor/GuarantorImportHandler.java | 2 + .../guarantor/GuarantorWorkbookPopulator.java | 5 +- .../BulkImportWorkbookServiceImpl.java | 13 +- .../EmailCampaignReadPlatformServiceImpl.java | 108 +- ...lConfigurationReadPlatformServiceImpl.java | 18 +- .../service/EmailReadPlatformServiceImpl.java | 36 +- .../sms/domain/SmsCampaignRepository.java | 4 + ...SmsCampaignNameAlreadyExistsException.java | 28 + .../sms/mapper/SmsCampaignMapper.java | 55 +- ...msCampaignWritePlatformServiceJpaImpl.java | 50 +- .../codes/api/CodeValuesApiResource.java | 8 +- .../api/GlobalConfigurationApiResource.java | 6 +- .../InternalConfigurationsApiResource.java | 2 + .../async/SpringAsyncConfig.java | 7 + .../domain/ConfigurationDomainServiceJpa.java | 15 + .../core/config/SecurityConfig.java | 41 +- .../core/config/SpringConfig.java | 58 +- .../core/config/TaskExecutorConstant.java | 1 + .../CreditBureauIntegrationApiResource.java | 2 + .../DatatableRejectionCleanupService.java | 55 + .../service/DatatableWriteServiceImpl.java | 2 +- .../FineractEntityAccessReadServiceImpl.java | 132 +- ...gsAccountForceWithdrawalBusinessEvent.java | 35 + .../mapper/loan/LoanChargeDataMapper.java | 1 + .../jobs/api/SchedulerJobApiResource.java | 13 +- .../jobs/filter/COBApiFilter.java | 95 + .../jobs/filter/COBFilterApiMatcher.java | 79 + .../jobs/filter/COBFilterHelper.java | 36 + .../jobs/filter/LoanCOBApiFilter.java | 73 +- .../jobs/filter/LoanCOBFilterHelper.java | 264 +- .../jobs/filter/LoanCOBFilterHelperImpl.java | 242 + .../ProgressiveLoanModelCheckerHelper.java | 67 +- .../WorkingCapitalLoanCOBApiFilter.java | 31 + .../WorkingCapitalLoanCOBFilterHelper.java | 22 + ...WorkingCapitalLoanCOBFilterHelperImpl.java | 209 + .../handler/ExecuteJobCommandHandler.java | 46 + .../jobs/service/InlineJobType.java | 4 +- .../AbstractJobParameterProvider.java | 6 +- .../LoanCOBJobParameterProvider.java | 5 +- .../service/PlatformUserDetailsChecker.java | 39 + .../service/SmsReadPlatformServiceImpl.java | 34 +- .../service/InteropServiceImpl.java | 4 +- .../starter/InteroperationConfiguration.java | 7 +- .../apache/fineract/mix/data/ContextData.java | 79 - .../mix/domain/MixTaxonomyMapping.java | 70 - ...axonomyMappingReadPlatformServiceImpl.java | 60 - ...xonomyMappingWritePlatformServiceImpl.java | 55 - .../MixTaxonomyReadPlatformServiceImpl.java | 71 - .../NamespaceReadPlatformServiceImpl.java | 66 - .../mix/starter/MixConfiguration.java | 62 - ...ioningCriteriaReadPlatformServiceImpl.java | 16 +- .../staff/api/StaffApiResource.java | 15 +- .../api/WorkingDaysApiResource.java | 42 +- .../command/WorkingDaysUpdateCommand.java | 28 + .../workingdays/data/WorkingDayValidator.java | 1 + .../workingdays/data/WorkingDaysData.java | 36 +- .../data/WorkingDaysUpdateRequest.java | 47 + .../WorkingDaysUpdateRequestValidator.java | 62 + .../data/WorkingDaysUpdateResponse.java | 43 + .../UpdateWorkingDaysCommandHandler.java | 41 +- .../WorkingDaysReadPlatformServiceImpl.java | 12 +- .../WorkingDaysWritePlatformService.java | 8 +- ...WritePlatformServiceJpaRepositoryImpl.java | 53 +- .../OrganisationWorkingDaysConfiguration.java | 6 +- .../api/AccountTransfersApiResource.java | 2 +- .../StandingInstructionDataValidator.java | 10 +- .../ExecuteStandingInstructionsTasklet.java | 3 +- .../mapper/AccountTransfersMapper.java | 80 +- ...countTransfersReadPlatformServiceImpl.java | 23 +- ...ountTransfersWritePlatformServiceImpl.java | 18 +- ...ructionHistoryReadPlatformServiceImpl.java | 4 +- ...ingInstructionReadPlatformServiceImpl.java | 4 +- ...ngInstructionWritePlatformServiceImpl.java | 6 +- .../api/ClientTransactionsApiResource.java | 10 +- .../client/api/ClientsApiResource.java | 13 +- .../api/LoanChargesApiResource.java | 36 +- .../api/LoanTransactionsApiResource.java | 20 +- .../loanaccount/api/LoansApiResource.java | 19 +- .../api/LoansApiResourceSwagger.java | 6 +- .../guarantor/data/GuarantorData.java | 4 + .../guarantor/domain/Guarantor.java | 39 +- .../service/GuarantorDomainServiceImpl.java | 2 +- ...ritePlatformServiceJpaRepositoryIImpl.java | 22 +- .../LoanDisbursementValidator.java | 20 +- .../LoanTransactionValidatorImpl.java | 11 +- .../service/LoanDisbursementService.java | 2 +- .../service/LoanReadPlatformServiceImpl.java | 71 +- ...WritePlatformServiceJpaRepositoryImpl.java | 4 +- .../api/LoanProductsApiResource.java | 8 +- ...ountOnHoldFundTransactionsApiResource.java | 2 + ...DepositAccountTransactionsApiResource.java | 3 + .../api/FixedDepositAccountsApiResource.java | 12 +- .../api/FixedDepositProductsApiResource.java | 11 +- ...DepositAccountTransactionsApiResource.java | 6 +- .../RecurringDepositAccountsApiResource.java | 15 +- .../RecurringDepositProductsApiResource.java | 17 +- .../api/SavingsAccountChargesApiResource.java | 2 +- ...SavingsAccountTransactionsApiResource.java | 82 +- .../api/SavingsAccountsApiResource.java | 229 +- .../api/SavingsProductsApiResource.java | 17 +- .../data/DepositProductDataValidator.java | 4 + .../domain/DepositAccountAssembler.java | 12 +- .../savings/domain/FixedDepositAccount.java | 5 +- .../domain/SavingsAccountAssembler.java | 10 +- .../SavingsAccountDomainServiceJpa.java | 9 +- ...thdrawalSavingsAccountCommandHandler.java} | 17 +- ...SavingsAccountReadPlatformServiceImpl.java | 5 +- ...WritePlatformServiceJpaRepositoryImpl.java | 63 + .../SearchReadPlatformServiceImpl.java | 19 +- .../SelfAccountTransferReadServiceImpl.java | 27 +- .../api/SelfSavingsProductsApiResource.java | 3 + .../savings/api/SelfSavingsApiResource.java | 11 +- .../api/SelfAuthenticationApiResource.java | 5 +- .../self/spm/api/SelfSpmApiResource.java | 2 + .../ShareAccountDataSerializer.java | 54 +- .../ShareAccountReadPlatformServiceImpl.java | 114 +- .../service/SharesEnumerations.java | 9 + .../fineract/spm/api/SpmApiResource.java | 2 +- .../ScorecardReadPlatformServiceImpl.java | 32 +- .../api/UsersApiResource.java | 18 +- ...WritePlatformServiceJpaRepositoryImpl.java | 17 +- .../src/main/resources/application.properties | 10 +- .../tenant-store/changelog-tenant-store.xml | 1 + ...tandardize_character_set_and_collation.xml | 28 + .../db/changelog/tenant/changelog-tenant.xml | 7 + .../tenant/parts/0002_initial_data.xml | 3478 ++----- .../0003_postgresql_specific_initial_data.xml | 582 +- .../0212_add_force_password_reset_config.xml | 42 + ...transaction_summary_adding_originators.xml | 2649 +++++ ...ial_balance_summary_adding_originators.xml | 439 + ..._summary_reports_add_buydown_fee_types.xml | 3263 ++++++ ...dd_unique_constraint_sms_campaign_name.xml | 29 + .../parts/0217_force_withdrawal_configs.xml | 74 + ...tandardize_character_set_and_collation.xml | 6 +- .../module/fineract-provider/persistence.xml | 13 +- .../command/CommandStrategyProviderTest.java | 3 + ...vingsAccountChargeCommandStrategyTest.java | 116 + .../LoanItemListenerStepDefinitions.java | 12 +- .../ApplyLoanLockTaskletStepDefinitions.java | 19 +- .../cob/loan/LoanCOBPartitionerTest.java | 24 +- .../loan/LoanItemReaderStepDefinitions.java | 18 +- .../fineract/cob/loan/LoanItemReaderTest.java | 28 +- .../loan/LoanItemWriterStepDefinitions.java | 3 +- ...ieveAllNonClosedLoanIdServiceImplTest.java | 3 +- .../InlineLoanCOBExecutorServiceImplTest.java | 11 +- .../core/config/SpringConfigTest.java | 316 + .../DatatableWriteServiceImplTest.java | 53 + ...entConfigurationValidationServiceTest.java | 4 +- .../jobs/filter/LoanCOBApiFilterTest.java | 22 +- .../jobs/filter/LoanCOBFilterHelperTest.java | 6 +- .../handler/ExecuteJobCommandHandlerTest.java | 64 + .../report/MixXbrlBuilderStepDefinitions.java | 16 +- .../MixXbrlTaxonomyStepDefinitions.java | 6 +- .../LoanAdjustmentServiceImplTest.java | 4 +- .../resources/application-test.properties | 2 +- .../FloatingRatesReadPlatformServiceImpl.java | 104 +- .../domain/InterestRateChart.java | 3 - .../domain/InterestRateChartFields.java | 17 +- .../domain/InterestRateChartSlabFields.java | 5 - .../SavingsTransactionBooleanValues.java | 18 + .../savings/domain/DepositTermDetail.java | 4 +- .../savings/domain/SavingsAccount.java | 51 +- .../SavingsAccountWritePlatformService.java | 5 +- .../InterestRateChartValidationTest.java | 22 + .../api/AuthenticationApiResource.java | 3 + .../PasswordResetRequiredException.java | 43 + .../PasswordResetRequiredExceptionMapper.java | 57 + ...SpringSecurityPlatformSecurityContext.java | 12 +- fineract-war/build.gradle | 1 + .../dependencies.gradle | 48 + ...gCapitalLoanAccountLockRepositoryImpl.java | 56 + .../WorkingCapitalAccountLockRepository.java | 27 + .../domain/WorkingCapitalLoanAccountLock.java | 38 + ...kingCapitalLoanCOBWorkerItemProcessor.java | 36 + ...WorkingCapitalLoanCOBWorkerItemWriter.java | 55 + .../ApplyWorkingCapitalLoanLockTasklet.java | 48 + ...rkingCapitalLoanCOBWorkerItemListener.java | 40 + ...WorkingCapitalLoanCOBWorkerItemWriter.java | 41 + .../WorkingCapitalAccountLockServiceImpl.java | 34 + .../WorkingCapitalLoanCOBConstant.java | 45 + ...COBCustomJobParametersResolverTasklet.java | 42 + ...ingCapitalLoanCOBManagerConfiguration.java | 108 + .../WorkingCapitalLoanCOBPartitioner.java | 61 + ...kingCapitalLoanCOBWorkerConfiguration.java | 146 + ...rkingCapitalLoanCOBWorkerItemListener.java | 40 + ...kingCapitalLoanCOBWorkerItemProcessor.java | 36 + ...WorkingCapitalLoanCOBWorkerItemReader.java | 68 + ...WorkingCapitalLoanCOBWorkerItemWriter.java | 41 + ...pitalLoanInlineCOBWorkerItemProcessor.java | 36 + ...orkingCapitalLoanLockingConfiguration.java | 44 + .../WorkingCapitalLoanLockingServiceImpl.java | 53 + ...ingCapitalLoanRetrieveIdConfiguration.java | 40 + .../WorkingCapitalLoanRetrieveIdService.java | 23 + ...rkingCapitalLoanRetrieveIdServiceImpl.java | 112 + .../businessstep/DummyBusinessStep.java | 46 + .../WorkingCapitalLoanCOBBusinessStep.java | 24 + .../WorkingCapitalLoanProductConstants.java | 69 + .../WorkingCapitalLoanProductApiResource.java | 249 + ...gCapitalLoanProductApiResourceSwagger.java | 457 + ...LoanProductConfigurableAttributesData.java | 43 + .../data/WorkingCapitalLoanProductData.java | 101 + .../WorkingCapitalPaymentAllocationData.java | 44 + ...lAdvancedPaymentAllocationsJsonParser.java | 123 + ...alAdvancedPaymentAllocationsValidator.java | 109 + .../WorkingCapitalAmortizationType.java | 63 + .../domain/WorkingCapitalLoan.java | 45 +- ...WorkingCapitalLoanPeriodFrequencyType.java | 63 + .../domain/WorkingCapitalLoanProduct.java | 119 +- ...italLoanProductConfigurableAttributes.java | 61 + ...ngCapitalLoanProductMinMaxConstraints.java | 53 + ...pitalLoanProductPaymentAllocationRule.java | 59 + ...orkingCapitalLoanProductRelatedDetail.java | 69 + .../WorkingCapitalPaymentAllocationType.java | 39 + ...talPaymentAllocationTypeListConverter.java | 42 + ...anProductDuplicateExternalIdException.java | 32 + ...italLoanProductDuplicateNameException.java | 31 + ...oanProductDuplicateShortNameException.java | 32 + ...ngCapitalLoanProductNotFoundException.java | 37 + ...rkingCapitalLoanProductCommandHandler.java | 42 + ...rkingCapitalLoanProductCommandHandler.java | 42 + ...rkingCapitalLoanProductCommandHandler.java | 42 + ...gCapitalLoanProductBasicDetailsMapper.java | 45 + .../WorkingCapitalLoanProductMapper.java | 140 + ...roductPaymentAllocationRuleRepository.java | 29 + .../WorkingCapitalLoanProductRepository.java | 76 + .../WorkingCapitalLoanRepository.java | 67 + ...orkingCapitalLoanProductDataValidator.java | 537 + ...oanProductReadBasicDetailsServiceImpl.java | 45 + ...CapitalLoanProductReadPlatformService.java | 35 + ...talLoanProductReadPlatformServiceImpl.java | 102 + .../WorkingCapitalLoanProductUpdateUtil.java | 204 + ...apitalLoanProductWritePlatformService.java | 31 + ...alLoanProductWritePlatformServiceImpl.java | 397 + .../module-changelog-master.xml | 5 +- .../parts/0001_loan_product.xml | 233 + .../parts/0002_wc_loan_schema.xml | 76 + .../parts/0003_working_capital_loan_cob.xml | 89 + ...004_extend_working_capital_loan_entity.xml | 58 + .../persistence.xml | 3 + ...ngCapitalLoanProductDataValidatorTest.java | 352 + integration-tests/build.gradle | 2 + integration-tests/dependencies.gradle | 2 +- ...ntAllocationLoanRepaymentScheduleTest.java | 10 + .../AuditIntegrationTest.java | 18 + .../BaseLoanIntegrationTest.java | 24 +- .../ClientLoanIntegrationTest.java | 4 +- .../CreditBureauConfigurationTest.java | 37 +- .../integrationtests/CreditBureauTest.java | 88 +- .../integrationtests/CurrenciesTest.java | 86 - ...FloatingRateInterestRecalculationTest.java | 227 + .../GroupSavingsIntegrationTest.java | 266 +- .../LoanProductBasicDetailsTest.java | 41 + .../integrationtests/MakercheckerTest.java | 76 + .../PasswordResetIntegrationTest.java | 126 + .../SavingsAccountForceWithdrawalTest.java | 113 + .../SavingsAccountsExternalIdTest.java | 27 +- .../integrationtests/SavingsAccountsTest.java | 6 +- .../integrationtests/SearchResourcesTest.java | 83 + .../SmsCampaignIntegrationTest.java | 115 + .../UserAdministrationTest.java | 14 +- .../WorkingCapitalLoanProductCRUDTest.java | 486 + ...rkingCapitalLoanProductValidationTest.java | 785 ++ .../client/ClientSearchTest.java | 8 +- .../integrationtests/client/ClientTest.java | 4 +- .../integrationtests/client/DocumentTest.java | 2 +- .../client/FeignDocumentTest.java | 2 +- .../client/FeignImageTest.java | 2 +- .../integrationtests/client/ImageTest.java | 2 +- .../client/IntegrationTest.java | 23 + .../integrationtests/client/ReportsTest.java | 4 +- .../integrationtests/client/StaffTest.java | 4 +- .../feign/helpers/FeignAccountHelper.java | 2 +- .../feign/helpers/FeignClientHelper.java | 4 +- .../helpers/FeignExternalEventHelper.java | 59 + .../FeignGlobalConfigurationHelper.java | 2 +- .../helpers/FeignJournalEntryHelper.java | 2 +- .../helpers/FeignLoanOriginatorHelper.java | 18 +- .../feign/helpers/FeignSchedulerHelper.java | 4 +- .../helpers/InternalExternalEventsApi.java | 41 + ...FeignLoanChargeOriginatorEnricherTest.java | 196 + .../FeignTrialBalanceSummaryReportTest.java | 12 +- .../integrationtests/common/CenterDomain.java | 4 +- .../integrationtests/common/ClientHelper.java | 17 +- .../CreditBureauConfigurationHelper.java | 200 +- .../common/CreditBureauIntegrationHelper.java | 53 +- .../common/CurrenciesHelper.java | 106 - .../common/CurrencyDomain.java | 158 - .../common/ExternalAssetOwnerHelper.java | 8 +- .../ExternalEventConfigurationHelper.java | 20 +- .../common/GlobalConfigurationHelper.java | 28 +- .../common/PaymentTypeHelper.java | 2 +- .../integrationtests/common/SurveyHelper.java | 4 +- .../integrationtests/common/Utils.java | 125 +- .../common/accounting/AccountHelper.java | 6 +- .../FinancialActivityAccountHelper.java | 6 +- .../common/accounting/JournalEntryHelper.java | 2 +- .../common/commands/MakercheckersHelper.java | 7 + .../common/loans/LoanProductHelper.java | 12 +- .../common/loans/LoanTransactionHelper.java | 273 +- .../common/organisation/CampaignsHelper.java | 21 +- .../common/savings/SavingsAccountHelper.java | 26 +- .../SavingsTestLifecycleExtension.java | 4 +- .../shares/ShareAccountIntegrationTests.java | 415 + .../WorkingCapitalLoanProductHelper.java | 83 + .../WorkingCapitalLoanProductTestBuilder.java | 350 + .../guarantor/GuarantorHelper.java | 19 + .../guarantor/GuarantorTestBuilder.java | 12 +- .../reaging/LoanReAgingIntegrationTest.java | 247 + .../LoanReAmortizationIntegrationTest.java | 94 + .../AccountTransferWithdrawalFeeTest.java | 16 +- .../base/BaseSavingsIntegrationTest.java | 18 +- kubernetes/fineractmysql-deployment.yml | 4 +- oauth2-tests/dependencies.gradle | 2 +- renovate.json | 42 +- settings.gradle | 1 + twofactor-tests/dependencies.gradle | 2 +- 603 files changed, 42664 insertions(+), 17328 deletions(-) create mode 100644 .github/workflows/pr-one-commit-per-user-check.yml create mode 100644 .github/workflows/verify-api-backward-compatibility.yml create mode 100644 config/docker/mariadb/conf.d/mariadb_compat.cnf create mode 100644 docker-compose-mysql.yml create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/common/CommonPartitioner.java rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java (96%) rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java (99%) create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/domain/AbstractLockingService.java create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLock.java create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLockRepository.java rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/domain/LockOwner.java (100%) rename fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java => fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockingService.java (71%) rename fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java => fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockCannotBeAppliedException.java (85%) rename fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanReadException.java => fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockedReadException.java (90%) rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java (84%) create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/processor/AbstractItemProcessor.java rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java (93%) rename {fineract-provider => fineract-cob}/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java (92%) rename fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java => fineract-cob/src/main/java/org/apache/fineract/cob/service/AbstractAccountLockService.java (71%) create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/service/AccountLockService.java create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/service/BeforeStepLockingItemReaderHelper.java rename fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java => fineract-cob/src/main/java/org/apache/fineract/cob/service/RetrieveIdService.java (85%) create mode 100644 fineract-cob/src/main/java/org/apache/fineract/cob/tasklet/ApplyCommonLockTasklet.java rename {fineract-provider => fineract-core}/src/main/java/org/apache/fineract/infrastructure/core/serialization/ThrowableSerialization.java (100%) create mode 100644 fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/CleanupService.java rename {fineract-provider => fineract-core}/src/main/java/org/apache/fineract/infrastructure/springbatch/PropertyService.java (100%) rename {fineract-provider => fineract-core}/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java (100%) rename {fineract-provider => fineract-core}/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java (100%) rename {fineract-provider => fineract-core}/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java (100%) create mode 100644 fineract-doc/src/docs/en/chapters/architecture/api-backward-compatibility.adoc create mode 100644 fineract-doc/src/docs/en/chapters/features/loan-origination-details.adoc create mode 100644 fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImplTest.java create mode 100644 fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/TestDatabaseConfiguration.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/WorkingCapitalLoanProduct.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkingCapitalLoanTestHelper.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepConfigurationStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/CurrencyStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/OfficeStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/WorkingCapitalLoanCobStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java create mode 100644 fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/reporting/ReportingStepDef.java create mode 100644 fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WcpCobBusinessStepInitializerStep.java create mode 100644 fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Currency.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Loan-Part1.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Loan-Part2.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Loan-Part3.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Loan-Part4.feature delete mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Loan.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/Reporting.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature create mode 100644 fineract-e2e-tests-runner/src/test/resources/features/WorkingCapital_COB.feature create mode 100644 fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanChargeDataV1OriginatorEnricher.java rename {fineract-provider => fineract-loan}/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java (100%) create mode 100644 fineract-loan/src/main/java/org/apache/fineract/cob/service/RetrieveLoanIdService.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsDetailsApiResource.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBasicDetailsData.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/LoanProductBasicDetailsMapper.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsService.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsServiceImpl.java create mode 100644 fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculatorTest.java create mode 100644 fineract-mix/build.gradle create mode 100644 fineract-mix/dependencies.gradle rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java (78%) rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java (79%) rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java (53%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/command/MixTaxonomyMappingUpdateCommand.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLContextData.java rename fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java => fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLData.java (76%) rename fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java => fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLNamespaceData.java (79%) rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java (78%) rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java (90%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateRequest.java rename fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyRequest.java => fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateResponse.java (89%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespace.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespaceRepository.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomy.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java rename {fineract-provider => fineract-mix}/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java (79%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyRepository.java rename fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java => fineract-mix/src/main/java/org/apache/fineract/mix/exception/MixReportXBRLMappingInvalidException.java (86%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/handler/MixTaxonomyMappingUpdateCommandHandler.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixReportXBRLNamespaceMapper.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMapper.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingMapper.java create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingUpdateRequestMapper.java rename fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLBuilder.java (80%) rename fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadService.java (80%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadServiceImpl.java rename fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultService.java (82%) rename fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultServiceImpl.java (56%) rename fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadService.java (94%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadServiceImpl.java rename fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteService.java (75%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteServiceImpl.java rename fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java => fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadService.java (95%) create mode 100644 fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadServiceImpl.java create mode 100644 fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/OverdueBalanceCorrection.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/api/COBCatchUpExecutorHelper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/api/WorkingCapitalLoanCOBCatchUpApiResource.java rename {fineract-cob => fineract-provider}/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java (98%) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/listener/WorkingCapitalChunkProcessingLoanItemListener.java rename fineract-provider/src/main/java/org/apache/fineract/cob/loan/{RetrieveAllNonClosedLoanIdServiceImpl.java => RetrieveAllNonClosedIdServiceImpl.java} (93%) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalInlineCOBLoanItemReader.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalLoanInlineCOBConfig.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCOBExecutorService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCommonCOBExecutorService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/COBCatchUpService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/CommonCOBCatchUpService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineCommonLockableCOBExecutorService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineWorkingCapitalLoanCOBExecutorServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableRejectionCleanupService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/savings/transaction/SavingsAccountForceWithdrawalBusinessEvent.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBApiFilter.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterApiMatcher.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterHelper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBApiFilter.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelperImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsChecker.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java rename fineract-provider/src/main/java/org/apache/fineract/{mix/handler/UpdateTaxonomyMappingCommandHandler.java => portfolio/savings/handler/ForceWithdrawalSavingsAccountCommandHandler.java} (68%) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0212_add_force_password_reset_config.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0213_transaction_summary_adding_originators.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0214_trial_balance_summary_adding_originators.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0215_transaction_summary_reports_add_buydown_fee_types.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0217_force_withdrawal_configs.xml rename fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_initial_schema.xml => fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml (86%) create mode 100644 fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/SpringConfigTest.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java create mode 100644 fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredException.java create mode 100644 fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredExceptionMapper.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/CustomWorkingCapitalLoanAccountLockRepositoryImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalAccountLockRepository.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalLoanAccountLock.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemProcessor.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemWriter.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/ApplyWorkingCapitalLoanLockTasklet.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemListener.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemWriter.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalAccountLockServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBConstant.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBCustomJobParametersResolverTasklet.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBManagerConfiguration.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBPartitioner.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerConfiguration.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemListener.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemProcessor.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemReader.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemWriter.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanInlineCOBWorkerItemProcessor.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingConfiguration.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdConfiguration.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdService.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DummyBusinessStep.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/WorkingCapitalLoanCOBBusinessStep.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/WorkingCapitalLoanProductConstants.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResource.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResourceSwagger.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductData.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalPaymentAllocationData.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsJsonParser.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsValidator.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAmortizationType.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanPeriodFrequencyType.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductMinMaxConstraints.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductPaymentAllocationRule.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductRelatedDetail.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationType.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationTypeListConverter.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateExternalIdException.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateNameException.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateShortNameException.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductNotFoundException.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/CreateWorkingCapitalLoanProductCommandHandler.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/DeleteWorkingCapitalLoanProductCommandHandler.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/UpdateWorkingCapitalLoanProductCommandHandler.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductBasicDetailsMapper.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductPaymentAllocationRuleRepository.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductRepository.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanRepository.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidator.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadBasicDetailsServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformService.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformService.java create mode 100644 fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java create mode 100644 fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_loan_product.xml create mode 100644 fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0002_wc_loan_schema.xml create mode 100644 fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0003_working_capital_loan_cob.xml create mode 100644 fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0004_extend_working_capital_loan_entity.xml create mode 100644 fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidatorTest.java delete mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/FloatingRateInterestRecalculationTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductBasicDetailsTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/PasswordResetIntegrationTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountForceWithdrawalTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductCRUDTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductValidationTest.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignExternalEventHelper.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/InternalExternalEventsApi.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanChargeOriginatorEnricherTest.java delete mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java delete mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductHelper.java create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductTestBuilder.java diff --git a/.asf.yaml b/.asf.yaml index 0d7d6037d8b..8ec1b8347e3 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -15,3 +15,6 @@ github: - savings - social-impact - tech4good +notifications: + commits: commits@fineract.apache.org + pullrequests: issues@fineract.apache.org diff --git a/.github/workflows/build-cucumber.yml b/.github/workflows/build-cucumber.yml index 788e998d81f..fb063ffd1fe 100644 --- a/.github/workflows/build-cucumber.yml +++ b/.github/workflows/build-cucumber.yml @@ -27,19 +27,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Cache Gradle dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.gradle/caches @@ -47,7 +47,7 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true @@ -87,7 +87,7 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-results-${{ matrix.task }} path: | @@ -98,7 +98,7 @@ jobs: - name: Archive Progressive Loan JAR if: matrix.job_type == 'progressive-loan' && always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: progressive-loan-jar path: ${{ env.EMBEDDABLE_JAR_FILE }} @@ -107,7 +107,7 @@ jobs: - name: Archive server logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: server-logs-${{ matrix.task }} path: '**/build/cargo/' diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 43e5717b2d7..18a6d6b6656 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -26,18 +26,18 @@ jobs: IMAGE_NAME: fineract steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Build the image run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml index cd67d21c115..8ff6efdc900 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/build-documentation.yml @@ -10,23 +10,23 @@ jobs: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22 - name: Congfigure vega-cli run: npm i -g vega-cli --unsafe - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v5.0.1 + uses: gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Install additional software run: | sudo apt-get update diff --git a/.github/workflows/build-e2e-tests.yml b/.github/workflows/build-e2e-tests.yml index 0c89f02bb9c..0ecd0b9afad 100644 --- a/.github/workflows/build-e2e-tests.yml +++ b/.github/workflows/build-e2e-tests.yml @@ -33,18 +33,18 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Make scripts executable run: chmod +x scripts/split-features.sh @@ -146,7 +146,7 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: allure-results-shard-${{ matrix.shard_index }} path: | @@ -159,7 +159,7 @@ jobs: - name: Upload Allure Report if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: allure-report-shard-${{ matrix.shard_index }} path: allure-report-shard-${{ matrix.shard_index }} @@ -167,7 +167,7 @@ jobs: - name: Upload logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: logs-shard-${{ matrix.shard_index }} path: | diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index d1c638994c2..ee057286024 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -17,7 +17,7 @@ jobs: services: mariadb: - image: mariadb:11.5.2 + image: mariadb:12.2 ports: - 3306:3306 env: @@ -44,19 +44,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Cache Gradle dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.gradle/caches @@ -64,16 +64,21 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true - name: Verify MariaDB connection run: | - while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do + while ! mysqladmin ping -h"127.0.0.1" -P3306 -uroot -pmysql --silent; do sleep 1 done + - name: Configure MariaDB compatibility defaults + run: | + mysql -h127.0.0.1 -P3306 -uroot -pmysql -e "SET GLOBAL innodb_snapshot_isolation=OFF;" + mysql -h127.0.0.1 -P3306 -uroot -pmysql -e "SHOW VARIABLES LIKE 'innodb_snapshot_isolation';" + - name: Initialise databases run: | ./gradlew --no-daemon -q createDB -PdbName=fineract_tenants @@ -135,7 +140,7 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-results-${{ matrix.task }} path: '**/build/reports/' @@ -143,7 +148,7 @@ jobs: - name: Archive server logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: server-logs-${{ matrix.task }} path: '**/build/cargo/' diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index f5e00117de4..065b33ab1a3 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -44,19 +44,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Cache Gradle dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.gradle/caches @@ -64,7 +64,7 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true @@ -135,7 +135,7 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-results-${{ matrix.task }} path: '**/build/reports/' @@ -143,7 +143,7 @@ jobs: - name: Archive server logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: server-logs-${{ matrix.task }} path: '**/build/cargo/' diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml index 378bd181e43..78a333e8c41 100644 --- a/.github/workflows/build-postgresql.yml +++ b/.github/workflows/build-postgresql.yml @@ -17,7 +17,7 @@ jobs: services: postgresql: - image: postgres:17.4 + image: postgres:18.3 ports: - 5432:5432 env: @@ -45,19 +45,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Cache Gradle dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.gradle/caches @@ -65,7 +65,7 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true @@ -136,7 +136,7 @@ jobs: - name: Archive test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-results-${{ matrix.task }} path: '**/build/reports/' @@ -144,7 +144,7 @@ jobs: - name: Archive server logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: server-logs-${{ matrix.task }} path: '**/build/cargo/' diff --git a/.github/workflows/liquibase-only-postgresql.yml b/.github/workflows/liquibase-only-postgresql.yml index a675141557d..d1215876cad 100644 --- a/.github/workflows/liquibase-only-postgresql.yml +++ b/.github/workflows/liquibase-only-postgresql.yml @@ -12,7 +12,7 @@ jobs: services: postgresql: - image: postgres:17.4 + image: postgres:18.3 ports: - 5432:5432 env: @@ -26,19 +26,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Cache Gradle dependencies - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.gradle/caches @@ -46,7 +46,7 @@ jobs: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true diff --git a/.github/workflows/mifos-fineract-client-publish.yml b/.github/workflows/mifos-fineract-client-publish.yml index 1394c8b089c..1e4bca410ed 100644 --- a/.github/workflows/mifos-fineract-client-publish.yml +++ b/.github/workflows/mifos-fineract-client-publish.yml @@ -18,19 +18,19 @@ jobs: steps: - name: Checkout Source Code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Generate build number - uses: onyxmueller/build-tag-number@v1 + uses: onyxmueller/build-tag-number@4a0c81c9af350d967032d49204c83c38e6b0c8e4 # v1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Build the image run: ./gradlew publish -Pfineract.config.username=$ARTIFACTORY_USERNAME -Pfineract.config.password=$ARTIFACTORY_PASSWORD -Pfineract.release.version=${BUILD_NUMBER} diff --git a/.github/workflows/pr-one-commit-per-user-check.yml b/.github/workflows/pr-one-commit-per-user-check.yml new file mode 100644 index 00000000000..3025a9d75f9 --- /dev/null +++ b/.github/workflows/pr-one-commit-per-user-check.yml @@ -0,0 +1,57 @@ +name: Fineract PR One Commit Per User Check + + +on: + pull_request: + types: [opened, reopened, synchronize] + + +permissions: + pull-requests: write + + +jobs: + verify-commits: + name: Validate One Commit Per User + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Verify Commit Policy + id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + + commits=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/commits") || { echo "::error::GitHub API request failed"; exit 1; } + + if echo "$commits" | jq -e '.[] | select(.author == null)' > /dev/null; then + echo "null_authors=true" >> $GITHUB_OUTPUT + echo "::error::Some commits have a git email that is not linked to a GitHub account. Please ensure your git email matches one of your GitHub Account emails.\n\nPlease also squash your commits to prevent this message again." + exit 1 + fi + + user_ids=$(echo "$commits" | jq -r '.[] | select(.author.type != "Bot") | .author.id') + if echo "$user_ids" | sort | uniq -d | grep -q .; then + echo "multiple_commits=true" >> $GITHUB_OUTPUT + echo "::error::Multiple commits from the same author have been detected." + exit 1 + fi + + echo "Success: Each author has exactly one commit." + + - name: Comment on PR + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + + if [ "${{ steps.check.outputs.null_authors }}" == "true" ]; then + gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body \ + $'**One Commit Per User Check Failed**\n\nSome committers have a git email that does not match their GitHub account. Please ensure your git email matches one of your GitHub Account emails. Please also squash your commits to prevent this message again.' + fi + + + if [ "${{ steps.check.outputs.multiple_commits }}" == "true" ]; then + gh pr comment ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --body \ + $'**One Commit Per User Check Failed**\n\nEach user may only have one commit per PR. Please squash your commits.' + fi diff --git a/.github/workflows/publish-dockerhub.yml b/.github/workflows/publish-dockerhub.yml index c8776878633..e38c399bc68 100644 --- a/.github/workflows/publish-dockerhub.yml +++ b/.github/workflows/publish-dockerhub.yml @@ -15,18 +15,18 @@ jobs: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - name: Checkout Source Code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Get Git Hashes run: | diff --git a/.github/workflows/run-integration-test-sequentially-postgresql.yml b/.github/workflows/run-integration-test-sequentially-postgresql.yml index 29afa1fcfcd..e409eabd259 100644 --- a/.github/workflows/run-integration-test-sequentially-postgresql.yml +++ b/.github/workflows/run-integration-test-sequentially-postgresql.yml @@ -14,7 +14,7 @@ jobs: services: postgresql: - image: postgres:17.4 + image: postgres:18.3 ports: - 5432:5432 env: @@ -33,18 +33,18 @@ jobs: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 fetch-tags: true - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true - name: Verify PostgreSQL connection @@ -84,7 +84,7 @@ jobs: ./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=postgresql - name: Archive test results if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: test-results retention-days: 5 @@ -95,7 +95,7 @@ jobs: oauth2-tests/build/reports/ - name: Archive server logs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: server-logs retention-days: 5 diff --git a/.github/workflows/smoke-messaging.yml b/.github/workflows/smoke-messaging.yml index 10a2e78711f..c74daad6c20 100644 --- a/.github/workflows/smoke-messaging.yml +++ b/.github/workflows/smoke-messaging.yml @@ -24,18 +24,18 @@ jobs: IMAGE_NAME: fineract steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 - name: Build the image run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 5394ba8eb95..c48ee8e94d5 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -20,16 +20,16 @@ jobs: JAVA_BINARIES: . steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: '21' distribution: 'zulu' - name: Setup Gradle and Validate Wrapper - uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 with: validate-wrappers: true - name: Build diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index d4691e97540..c92027ecb10 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # stale-issue-message: 'Stale issue message' diff --git a/.github/workflows/verify-api-backward-compatibility.yml b/.github/workflows/verify-api-backward-compatibility.yml new file mode 100644 index 00000000000..7f3d71d04ab --- /dev/null +++ b/.github/workflows/verify-api-backward-compatibility.yml @@ -0,0 +1,243 @@ +name: Verify API Backward Compatibility + +on: [pull_request] + +permissions: + contents: read + pull-requests: write + +jobs: + api-compatibility-check: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + env: + TZ: Asia/Kolkata + + steps: + - name: Checkout base branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: ${{ github.event.pull_request.base.repo.full_name }} + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + path: baseline + + - name: Checkout PR branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + path: current + + - name: Compute merge-base commit + id: merge-base + run: | + cd baseline + # For fork PRs, fetch PR head from the local current/ checkout + git fetch "${GITHUB_WORKSPACE}/current" HEAD --no-tags 2>/dev/null || true + MERGE_BASE=$(git merge-base ${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.sha }}) + echo "Merge-base commit: ${MERGE_BASE}" + echo "sha=${MERGE_BASE}" >> "$GITHUB_OUTPUT" + BASE_HEAD=$(git rev-parse ${{ github.event.pull_request.base.ref }}) + if [ "${MERGE_BASE}" != "${BASE_HEAD}" ]; then + echo "::notice::PR is not rebased on latest ${{ github.event.pull_request.base.ref }}. Using merge-base ${MERGE_BASE} as baseline (branch HEAD: ${BASE_HEAD})." + fi + + - name: Reset baseline to merge-base + working-directory: baseline + run: git checkout ${{ steps.merge-base.outputs.sha }} + + - name: Set up JDK 21 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: 'zulu' + java-version: '21' + + - name: Generate baseline spec + working-directory: baseline + run: ./gradlew :fineract-provider:resolve --no-daemon + + - name: Generate PR spec + working-directory: current + run: ./gradlew :fineract-provider:resolve --no-daemon + + - name: Sanitize specs + run: | + python3 -c " + import json, sys + + def sanitize(path): + with open(path) as f: + spec = json.load(f) + fixed = 0 + for path_item in spec.get('paths', {}).values(): + for op in path_item.values(): + if not isinstance(op, dict) or 'requestBody' not in op: + continue + for media in op['requestBody'].get('content', {}).values(): + if 'schema' not in media: + media['schema'] = {'type': 'object'} + fixed += 1 + if fixed: + with open(path, 'w') as f: + json.dump(spec, f) + print(f'{path}: fixed {fixed} entries') + + sanitize('${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json') + sanitize('${GITHUB_WORKSPACE}/current/fineract-provider/build/resources/main/static/fineract.json') + " + + - name: Check breaking changes + id: breaking-check + continue-on-error: true + working-directory: current + run: | + set -o pipefail + ./gradlew :fineract-provider:checkBreakingChanges \ + -PapiBaseline="${GITHUB_WORKSPACE}/baseline/fineract-provider/build/resources/main/static/fineract.json" \ + --no-daemon --quiet 2>&1 | tail -50 + + - name: Build report + if: steps.breaking-check.outcome == 'failure' + id: report + run: | + REPORT_DIR="current/fineract-provider/build/swagger-brake" + + python3 -c " + import json, glob, os + from collections import defaultdict + + RULE_DESC = { + 'R001': 'Standard API changed to beta', + 'R002': 'Path deleted', + 'R003': 'Request media type deleted', + 'R004': 'Request parameter deleted', + 'R005': 'Request parameter enum value deleted', + 'R006': 'Request parameter location changed', + 'R007': 'Request parameter made required', + 'R008': 'Request parameter type changed', + 'R009': 'Request attribute removed', + 'R010': 'Request type changed', + 'R011': 'Request enum value deleted', + 'R012': 'Response code deleted', + 'R013': 'Response media type deleted', + 'R014': 'Response attribute removed', + 'R015': 'Response type changed', + 'R016': 'Response enum value deleted', + 'R017': 'Request parameter constraint changed', + } + + report_dir = '${REPORT_DIR}' + files = sorted(glob.glob(os.path.join(report_dir, '*.json'))) + if not files: + body = 'Breaking change detected but no report file found.' + else: + with open(files[0]) as f: + data = json.load(f) + + all_changes = [] + for items in data.get('breakingChanges', {}).values(): + all_changes.extend(items) + + if not all_changes: + body = 'Breaking change detected but no details available in report.' + else: + def detail(c): + for key in ('attributeName', 'attribute', 'name', 'mediaType', 'enumValue', 'code'): + v = c.get(key) + if v: + val = v.rsplit('.', 1)[-1] + if key in ('attributeName', 'attribute', 'name'): + return val + return f'{key}={val}' + return '-' + + groups = defaultdict(list) + for c in all_changes: + groups[(c.get('ruleCode', '?'), detail(c))].append(c) + + lines = [] + lines.append('| Rule | Description | Detail | Affected endpoints | Count |') + lines.append('|------|-------------|--------|--------------------|-------|') + for (rule, det), items in sorted(groups.items()): + desc = RULE_DESC.get(rule, '') + eps = sorted(set( + f'{c.get(\"method\",\"\")} {c.get(\"path\",\"\")}' + for c in items if c.get('path') + )) + ep_str = ', '.join(f'\`{e}\`' for e in eps[:5]) + if len(eps) > 5: + ep_str += f' +{len(eps)-5} more' + lines.append(f'| {rule} | {desc} | \`{det}\` | {ep_str} | {len(items)} |') + + lines.append('') + lines.append(f'**Total: {len(all_changes)} violations across {len(groups)} unique changes**') + body = '\n'.join(lines) + + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('has_report=true\n') + + report_file = '${GITHUB_WORKSPACE}/breaking-changes-report.md' + with open(report_file, 'w') as f: + f.write('## Breaking API Changes Detected\n\n') + f.write(body) + f.write('\n\n> **Note:** This check is informational only and does not block the PR.\n') + + # Also write to step summary + with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as f: + f.write('## Breaking API Changes Detected\n\n') + f.write(body) + f.write('\n\n> **Note:** This check is informational only and does not block the PR.\n') + " + + - name: Comment on PR + if: always() + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + MARKER="" + + # Find existing comment by marker + COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ + --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" | head -1) + + if [ "${{ steps.breaking-check.outcome }}" == "failure" ] && [ -f "${GITHUB_WORKSPACE}/breaking-changes-report.md" ]; then + # Prepend marker to the report + BODY="${MARKER} + $(cat ${GITHUB_WORKSPACE}/breaking-changes-report.md)" + + if [ -n "$COMMENT_ID" ]; then + gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \ + -X PATCH -f body="${BODY}" + else + gh pr comment "${PR_NUMBER}" --repo ${{ github.repository }} --body "${BODY}" + fi + elif [ -n "$COMMENT_ID" ]; then + # No breaking changes anymore, delete the old comment + gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" -X DELETE + fi + + - name: Report no breaking changes + if: steps.breaking-check.outcome == 'success' + run: | + echo "## No Breaking API Changes Detected" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The API contract is backward compatible." >> $GITHUB_STEP_SUMMARY + + - name: Archive breaking change report + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: api-compatibility-report + path: current/fineract-provider/build/swagger-brake/ + retention-days: 30 + + - name: Fail if breaking changes detected + if: steps.breaking-check.outcome == 'failure' + run: | + echo "::error::Breaking API changes detected. See the report above for details." + exit 1 diff --git a/.github/workflows/verify-commits.yml b/.github/workflows/verify-commits.yml index 69dfca6e10e..0efc73d081e 100644 --- a/.github/workflows/verify-commits.yml +++ b/.github/workflows/verify-commits.yml @@ -31,7 +31,7 @@ jobs: timeout-minutes: 1 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -43,5 +43,5 @@ jobs: chmod +x scripts/verify-signed-commits.sh ./scripts/verify-signed-commits.sh \ --base-ref origin/${{ github.base_ref }} \ - --head-ref ${{ github.sha }} \ + --head-ref ${{ github.event.pull_request.head.sha }} \ --strict diff --git a/.github/workflows/verify-liquibase-backward-compatibility.yml b/.github/workflows/verify-liquibase-backward-compatibility.yml index 400c9f283ce..56d7e25824e 100644 --- a/.github/workflows/verify-liquibase-backward-compatibility.yml +++ b/.github/workflows/verify-liquibase-backward-compatibility.yml @@ -12,7 +12,7 @@ jobs: services: postgresql: - image: postgres:17.4 + image: postgres:18.3 ports: - 5432:5432 env: @@ -32,14 +32,14 @@ jobs: steps: - name: Checkout the base branch (`develop`) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ github.event.pull_request.base.repo.full_name }} ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 - name: Set up JDK 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'zulu' java-version: '21' @@ -107,7 +107,7 @@ jobs: sleep 10 - name: Checkout the PR branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} diff --git a/README.md b/README.md index ca7a95b6151..20b246cdd73 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ In the moment you get started writing code, please consult our [CONTRIBUTING](CO REQUIREMENTS ============ * min. 16GB RAM and 8 core CPU -* `MariaDB >= 11.5.2` or `PostgreSQL >= 17.0` +* `MariaDB >= 12.2` or `PostgreSQL >= 18.0` * `Java >= 21` (Azul Zulu JVM is tested by our CI on GitHub Actions) Tomcat (min. v10) is only required, if you wish to deploy the Fineract WAR to a separate external servlet container. You do not need to install Tomcat to run Fineract. We recommend the use of the self-contained JAR, which transparently embeds a servlet container using Spring Boot. @@ -293,11 +293,11 @@ DATABASE AND TABLES You can run the required version of the database server in a container, instead of having to install it, like this: - docker run --name mariadb-11.5 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:11.5.2 + docker run --name mariadb-12.2 -p 3306:3306 -e MARIADB_ROOT_PASSWORD=mysql -d mariadb:12.2.2 --innodb-snapshot-isolation=OFF and stop and destroy it like this: - docker rm -f mariadb-11.5 + docker rm -f mariadb-12.2 Beware that this container database keeps its state inside the container and not on the host filesystem. It is lost when you destroy (rm) this container. This is typically fine for development. See [Caveats: Where to Store Data on the database container documentation](https://hub.docker.com/_/mariadb) regarding how to make it persistent instead of ephemeral. diff --git a/build.gradle b/build.gradle index aa541da18d1..e72d73b1163 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ buildscript { 'fineract-loan', 'fineract-savings', 'fineract-report', + 'fineract-mix', 'integration-tests', 'twofactor-tests', 'oauth2-tests', @@ -75,6 +76,7 @@ buildscript { 'fineract-loan', 'fineract-savings', 'fineract-report', + 'fineract-mix', 'fineract-branch', 'fineract-document', 'fineract-progressive-loan', @@ -121,9 +123,10 @@ plugins { id 'se.thinkcode.cucumber-runner' version '0.0.11' apply false id "com.github.davidmc24.gradle.plugin.avro-base" version "1.9.1" apply false id 'org.openapi.generator' version '7.8.0' apply false - id 'com.gradleup.shadow' version '8.3.5' apply false + id 'com.gradleup.shadow' version '9.3.2' apply false id 'me.champeau.jmh' version '0.7.1' apply false id 'org.cyclonedx.bom' version '3.1.0' apply false + id 'com.docktape.swagger-brake' version '2.7.0' apply false } apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle" diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index 271bf5d8a42..cfd2b590c11 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -246,11 +246,11 @@ dependencyManagement { dependency "org.apache.avro:avro:1.12.0" - dependency ('org.mariadb.jdbc:mariadb-java-client:3.5.2') { + dependency ('org.mariadb.jdbc:mariadb-java-client:3.5.7') { exclude 'org.slf4j:jcl-over-slf4j' exclude 'org.slf4j:slf4j-api' } - dependency 'org.postgresql:postgresql:42.7.8' + dependency 'org.postgresql:postgresql:42.7.9' dependency 'com.mysql:mysql-connector-j:9.3.0' @@ -303,8 +303,8 @@ dependencyManagement { // Force lz4-java version: CVE-2025-12183 dependency 'at.yawk.lz4:lz4-java:1.10.1' // Force tomcat-embed-core version: CVE-2025-24813 - dependency 'org.apache.tomcat.embed:tomcat-embed-core:10.1.47' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:10.1.47' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:10.1.47' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:10.1.49' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:10.1.49' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:10.1.49' } } diff --git a/config/docker/compose/mariadb.yml b/config/docker/compose/mariadb.yml index 6756974f784..3b772cf6039 100644 --- a/config/docker/compose/mariadb.yml +++ b/config/docker/compose/mariadb.yml @@ -18,9 +18,10 @@ version: "3.8" services: mariadb: container_name: mariadb - image: mariadb:11.4 + image: mariadb:12.2 volumes: - ${PWD}/config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ${PWD}/config/docker/mariadb/conf.d/mariadb_compat.cnf:/etc/mysql/conf.d/mariadb_compat.cnf:ro - ${PWD}/config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro restart: always env_file: diff --git a/config/docker/compose/postgresql.yml b/config/docker/compose/postgresql.yml index 878c88264fd..fecd9ca5c03 100644 --- a/config/docker/compose/postgresql.yml +++ b/config/docker/compose/postgresql.yml @@ -20,7 +20,7 @@ version: "3.8" services: postgresql: - image: postgres:16.1 + image: postgres:18.3 volumes: - ${PWD}/config/docker/postgresql/docker-entrypoint-initdb.d/01-init.sh:/docker-entrypoint-initdb.d/01-init.sh:Z,ro restart: always diff --git a/config/docker/mariadb/conf.d/mariadb_compat.cnf b/config/docker/mariadb/conf.d/mariadb_compat.cnf new file mode 100644 index 00000000000..a4293b2e8a1 --- /dev/null +++ b/config/docker/mariadb/conf.d/mariadb_compat.cnf @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[mysqld] +innodb_snapshot_isolation=OFF diff --git a/docker-compose-mariadb.yml b/docker-compose-mariadb.yml index 59f18160b9f..fa4d5eca3c3 100644 --- a/docker-compose-mariadb.yml +++ b/docker-compose-mariadb.yml @@ -19,9 +19,10 @@ services: mariadb: container_name: mariadb - image: mariadb:11.4 + image: mariadb:12.2 volumes: - ./config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ./config/docker/mariadb/conf.d/mariadb_compat.cnf:/etc/mysql/conf.d/mariadb_compat.cnf:ro - ./config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro restart: always env_file: diff --git a/docker-compose-mysql.yml b/docker-compose-mysql.yml new file mode 100644 index 00000000000..5f0521724ad --- /dev/null +++ b/docker-compose-mysql.yml @@ -0,0 +1,34 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +services: + mysql: + container_name: mysql + image: mysql:8 + volumes: + - ${PWD}/config/docker/mysql/conf.d/server_collation.cnf:/etc/mysql/conf.d/server_collation.cnf:ro + - ${PWD}/config/docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:Z,ro + restart: always + env_file: + - ${PWD}/config/docker/env/mysql.env + healthcheck: + test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ] + timeout: 10s + retries: 10 + ports: + - "3306:3306" diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResourceSwagger.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResourceSwagger.java index 8faa5050fa9..5e2f366961d 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResourceSwagger.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/closure/api/GLClosuresApiResourceSwagger.java @@ -21,22 +21,20 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; +/** + * Swagger response and request schemas for GL Closures. + */ final class GLClosuresApiResourceSwagger { private GLClosuresApiResourceSwagger() { // don't allow to instantiate; use only for live API documentation } - /** - * TODO: describe where this belongs: {@link GLClosuresApiResource } {@link GLClosuresApiResource} - */ - // Check !! - @Schema(description = "GetGLClosureResponse") public static final class GetGlClosureResponse { private GetGlClosureResponse() { - // dont allow to initiatiate + // dont allow to instantiation } @Schema(example = "7") @@ -45,13 +43,13 @@ private GetGlClosureResponse() { public Long officeId; @Schema(example = "Head Office") public String officeName; - @Schema(example = "2013,1,2") + @Schema(example = "2013-01-02") public LocalDate closingDate; @Schema(example = "false") public boolean deleted; - @Schema(example = "2013,1,3") + @Schema(example = "2013-1-3") public LocalDate createdDate; - @Schema(example = "2013,1,3") + @Schema(example = "2013-1-3") public LocalDate lastUpdatedDate; @Schema(example = "1") public Long createdByUserId; @@ -66,7 +64,7 @@ private GetGlClosureResponse() { } - @Schema(description = "PostGLCLosuresRequest") + @Schema(description = "PostGLClosuresRequest") public static final class PostGlClosuresRequest { private PostGlClosuresRequest() { @@ -77,7 +75,7 @@ private PostGlClosuresRequest() { public Long officeId; @Schema(example = "06 December 2012") public LocalDate closingDate; - @Schema(example = "The accountants are heading for a carribean vacation") + @Schema(example = "The accountants are heading for a Caribbean vacation") public String comments; @Schema(example = "en") public String locale; diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java index 41adc66c32f..889db5dd564 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/financialactivityaccount/api/FinancialActivityAccountsApiResource.java @@ -119,7 +119,7 @@ public FinancialActivityAccountData retreive(@PathParam("mappingId") @Parameter( @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a new Financial Activity to Accounts Mapping", description = """ + @Operation(summary = "Create a new Financial Activity to Accounts Mapping", operationId = "createGLAccountMappingFinancialActivityAccount", description = """ Mandatory Fields financialActivityId, glAccountId""") @RequestBody(content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class))) @@ -135,7 +135,7 @@ public CommandProcessingResult createGLAccount(@Parameter(hidden = true) Financi @Path("{mappingId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Financial Activity to Account Mapping", description = "the API updates the Ledger account linked to a Financial Activity") + @Operation(summary = "Update a Financial Activity to Account Mapping", operationId = "updateGLAccountMappingFinancialActivityAccount", description = "the API updates the Ledger account linked to a Financial Activity") @RequestBody(content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PostFinancialActivityAccountsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.PutFinancialActivityAccountsResponse.class))) public CommandProcessingResult updateGLAccount(@PathParam("mappingId") @Parameter(description = "mappingId") final Long mappingId, @@ -150,7 +150,7 @@ public CommandProcessingResult updateGLAccount(@PathParam("mappingId") @Paramete @Path("{mappingId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Financial Activity to Account Mapping") + @Operation(summary = "Delete a Financial Activity to Account Mapping", operationId = "deleteGLAccountMappingFinancialActivityAccount") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FinancialActivityAccountsApiResourceSwagger.DeleteFinancialActivityAccountsResponse.class))) public CommandProcessingResult deleteGLAccount(@PathParam("mappingId") @Parameter(description = "mappingId") final Long mappingId) { final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteOfficeToGLAccountMapping(mappingId).build(); diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java index 03ec4c66bba..474add0243c 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/glaccount/api/GLAccountsApiResource.java @@ -181,11 +181,12 @@ public GLAccountData retreiveAccount(@PathParam("glAccountId") @Parameter(descri @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(tags = { "General Ledger Account" }, summary = "Create a General Ledger Account", description = """ - Note: You may optionally create Hierarchical Chart of Accounts by using the "parentId" property of an Account - Mandatory Fields: - name, glCode, type, usage and manualEntriesAllowed - """) + @Operation(tags = { + "General Ledger Account" }, summary = "Create a General Ledger Account", operationId = "createGLAccount", description = """ + Note: You may optionally create Hierarchical Chart of Accounts by using the "parentId" property of an Account + Mandatory Fields: + name, glCode, type, usage and manualEntriesAllowed + """) @RequestBody(content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PostGLAccountsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PostGLAccountsResponse.class))) public CommandProcessingResult createGLAccount(@Parameter(hidden = true) GLAccountCommand glAccountCommand) { @@ -198,7 +199,8 @@ public CommandProcessingResult createGLAccount(@Parameter(hidden = true) GLAccou @Path("{glAccountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(tags = { "General Ledger Account" }, summary = "Update a GL Account", description = "Updates a GL Account") + @Operation(tags = { + "General Ledger Account" }, summary = "Update a GL Account", operationId = "updateGLAccount", description = "Updates a GL Account") @RequestBody(content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PutGLAccountsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.PutGLAccountsResponse.class))) public CommandProcessingResult updateGLAccount(@PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId, @@ -212,7 +214,8 @@ public CommandProcessingResult updateGLAccount(@PathParam("glAccountId") @Parame @Path("{glAccountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(tags = { "General Ledger Account" }, summary = "Delete a GL Account", description = "Deletes a GL Account") + @Operation(tags = { + "General Ledger Account" }, summary = "Delete a GL Account", operationId = "deleteGLAccount", description = "Deletes a GL Account") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.DeleteGLAccountsResponse.class))) public CommandProcessingResult deleteGLAccount( @PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId) { diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java index bd2bf8ad610..d34c559719a 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesReadPlatformServiceImpl.java @@ -114,11 +114,11 @@ public ProvisioningEntryData retrieveProvisioningEntryData(Long entryId) { private static final class ProvisioningEntryDataMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder() - .append(" entry.id, entry.journal_entry_created, entry.createdby_id, entry.created_date, created.username as createduser,") - .append("entry.lastmodifiedby_id, modified.username as modifieduser, entry.lastmodified_date ") - .append("from m_provisioning_history entry ").append("left JOIN m_appuser created ON created.id = entry.createdby_id ") - .append("left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id "); + private static final String PROVISIONING_ENTRY_SCHEMA = """ + entry.id, entry.journal_entry_created, entry.createdby_id, entry.created_date, created.username as createduser, + entry.lastmodifiedby_id, modified.username as modifieduser, entry.lastmodified_date + from m_provisioning_history entry left JOIN m_appuser created ON created.id = entry.createdby_id + left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id\s"""; @Override @SuppressWarnings("unused") @@ -138,22 +138,22 @@ public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLExceptio } public String getSchema() { - return sqlQuery.toString(); + return PROVISIONING_ENTRY_SCHEMA; } } private static final class LoanProductProvisioningEntryRowMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder().append( - " entry.id, entry.history_id as historyId, office_id, entry.criteria_id as criteriaid, office.name as officename, product.name as productname, entry.product_id, ") - .append("category_id, category.category_name, liability.id as liabilityid, liability.gl_code as liabilitycode, liability.name as liabilityname, ") - .append("expense.id as expenseid, expense.gl_code as expensecode, expense.name as expensename, entry.currency_code, entry.overdue_in_days, entry.reseve_amount from m_loanproduct_provisioning_entry entry ") - .append("left join m_office office ON office.id = entry.office_id ") - .append("left join m_product_loan product ON product.id = entry.product_id ") - .append("left join m_provision_category category ON category.id = entry.category_id ") - .append("left join acc_gl_account liability ON liability.id = entry.liability_account ") - .append("left join acc_gl_account expense ON expense.id = entry.expense_account "); + private static final String LOAN_PRODUCT_PROVISIONING_ENTRY_SCHEMA = """ + entry.id, entry.history_id as historyId, office_id, entry.criteria_id as criteriaid, office.name as officename, product.name as productname, entry.product_id, + category_id, category.category_name, liability.id as liabilityid, liability.gl_code as liabilitycode, liability.name as liabilityname, + expense.id as expenseid, expense.gl_code as expensecode, expense.name as expensename, entry.currency_code, entry.overdue_in_days, entry.reseve_amount from m_loanproduct_provisioning_entry entry + left join m_office office ON office.id = entry.office_id + left join m_product_loan product ON product.id = entry.product_id + left join m_provision_category category ON category.id = entry.category_id + left join acc_gl_account liability ON liability.id = entry.liability_account + left join acc_gl_account expense ON expense.id = entry.expense_account\s"""; @Override @SuppressWarnings("unused") @@ -185,19 +185,19 @@ public LoanProductProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws } public String getSchema() { - return sqlQuery.toString(); + return LOAN_PRODUCT_PROVISIONING_ENTRY_SCHEMA; } } private static final class ProvisioningEntryDataMapperWithSumReserved implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder() - .append(" entry.id, journal_entry_created, createdby_id, created_date, created.username as createduser,") - .append("lastmodifiedby_id, modified.username as modifieduser, lastmodified_date, SUM(reserved.reseve_amount) as totalreserved ") - .append("from m_provisioning_history entry ") - .append("JOIN m_loanproduct_provisioning_entry reserved on entry.id = reserved.history_id ") - .append("left JOIN m_appuser created ON created.id = entry.createdby_id ") - .append("left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id "); + private static final String PROVISIONING_ENTRY_SUM_RESERVED_SCHEMA = """ + entry.id, journal_entry_created, createdby_id, created_date, created.username as createduser, + lastmodifiedby_id, modified.username as modifieduser, lastmodified_date, SUM(reserved.reseve_amount) as totalreserved + from m_provisioning_history entry + JOIN m_loanproduct_provisioning_entry reserved on entry.id = reserved.history_id + left JOIN m_appuser created ON created.id = entry.createdby_id + left JOIN m_appuser modified ON modified.id = entry.lastmodifiedby_id\s"""; @Override @SuppressWarnings("unused") @@ -217,7 +217,7 @@ public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLExceptio } public String getSchema() { - return sqlQuery.toString(); + return PROVISIONING_ENTRY_SUM_RESERVED_SCHEMA; } } @@ -286,9 +286,10 @@ public ProvisioningEntryData retrieveExistingProvisioningIdDateWithJournals() { private static final class ProvisioningEntryIdDateRowMapper implements RowMapper { - StringBuilder buff = new StringBuilder().append("select history1.id, history1.created_date from m_provisioning_history history1 ") - .append("where history1.created_date = (select max(history2.created_date) from m_provisioning_history history2 ") - .append("where history2.journal_entry_created='1')"); + private static final String PROVISIONING_ENTRY_ID_DATE_SCHEMA = """ + select history1.id, history1.created_date from m_provisioning_history history1 + where history1.created_date = (select max(history2.created_date) from m_provisioning_history history2 + where history2.journal_entry_created='1')\s"""; @Override public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLException { @@ -306,7 +307,7 @@ public ProvisioningEntryData mapRow(ResultSet rs, int rowNum) throws SQLExceptio } public String schema() { - return buff.toString(); + return PROVISIONING_ENTRY_ID_DATE_SCHEMA; } } diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc index 9eaa4adbced..e4f8022e1e2 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanChargeDataV1.avsc @@ -267,6 +267,18 @@ "type": "map" } ] + }, + { + "default": null, + "name": "originators", + "doc": "List of originators attached to the parent loan for revenue sharing", + "type": [ + "null", + { + "type": "array", + "items": "org.apache.fineract.avro.loan.v1.OriginatorDetailsV1" + } + ] } ] } diff --git a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java index 61a3108f5ee..c590c61f93f 100644 --- a/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java +++ b/fineract-client-feign/src/main/java/org/apache/fineract/client/feign/FineractFeignClient.java @@ -172,6 +172,8 @@ import org.apache.fineract.client.feign.services.TwoFactorApi; import org.apache.fineract.client.feign.services.UserGeneratedDocumentsApi; import org.apache.fineract.client.feign.services.UsersApi; +import org.apache.fineract.client.feign.services.WorkingCapitalLoanCobCatchUpApi; +import org.apache.fineract.client.feign.services.WorkingCapitalLoanProductsApi; import org.apache.fineract.client.feign.services.WorkingDaysApi; /** @@ -837,6 +839,14 @@ public UsersApi users() { return create(UsersApi.class); } + public WorkingCapitalLoanProductsApi workingCapitalLoanProducts() { + return create(WorkingCapitalLoanProductsApi.class); + } + + public WorkingCapitalLoanCobCatchUpApi workingCapitalLoanCobCatchUpApi() { + return create(WorkingCapitalLoanCobCatchUpApi.class); + } + public WorkingDaysApi workingDays() { return create(WorkingDaysApi.class); } diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 23f3dff5b3e..6f47fffb876 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -59,6 +59,7 @@ import org.apache.fineract.client.services.CodeValuesApi; import org.apache.fineract.client.services.CodesApi; import org.apache.fineract.client.services.CreditBureauConfigurationApi; +import org.apache.fineract.client.services.CreditBureauIntegrationApi; import org.apache.fineract.client.services.CurrencyApi; import org.apache.fineract.client.services.DataTablesApi; import org.apache.fineract.client.services.DefaultApi; @@ -95,6 +96,7 @@ import org.apache.fineract.client.services.LoanDisbursementDetailsApi; import org.apache.fineract.client.services.LoanInterestPauseApi; import org.apache.fineract.client.services.LoanProductsApi; +import org.apache.fineract.client.services.LoanProductsDetailsApi; import org.apache.fineract.client.services.LoanReschedulingApi; import org.apache.fineract.client.services.LoanTransactionsApi; import org.apache.fineract.client.services.LoansApi; @@ -201,6 +203,7 @@ public final class FineractClient { public final ChargesApi charges; public final ClientApi clients; public final CreditBureauConfigurationApi creditBureauConfiguration; + public final CreditBureauIntegrationApi creditBureauIntegration; public final ClientSearchV2Api clientSearchV2; public final ClientChargesApi clientCharges; @@ -238,6 +241,7 @@ public final class FineractClient { public final LoanCollateralApi loanCollaterals; public final LoanCapitalizedIncomeApi loanCapitalizedIncome; public final LoanProductsApi loanProducts; + public final LoanProductsDetailsApi loanProductsDetails; public final LoanReschedulingApi loanSchedules; public final LoansPointInTimeApi loansPointInTimeApi; public final LoansApi loans; @@ -336,6 +340,7 @@ private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { charges = retrofit.create(ChargesApi.class); clients = retrofit.create(ClientApi.class); creditBureauConfiguration = retrofit.create(CreditBureauConfigurationApi.class); + creditBureauIntegration = retrofit.create(CreditBureauIntegrationApi.class); clientSearchV2 = retrofit.create(ClientSearchV2Api.class); clientCharges = retrofit.create(ClientChargesApi.class); clientIdentifiers = retrofit.create(ClientIdentifierApi.class); @@ -371,6 +376,7 @@ private FineractClient(OkHttpClient okHttpClient, Retrofit retrofit) { loanCollaterals = retrofit.create(LoanCollateralApi.class); loanCapitalizedIncome = retrofit.create(LoanCapitalizedIncomeApi.class); loanProducts = retrofit.create(LoanProductsApi.class); + loanProductsDetails = retrofit.create(LoanProductsDetailsApi.class); loanSchedules = retrofit.create(LoanReschedulingApi.class); loansPointInTimeApi = retrofit.create(LoansPointInTimeApi.class); loans = retrofit.create(LoansApi.class); diff --git a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java index 9e4950b56f8..93676262e94 100644 --- a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java +++ b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientDemo.java @@ -41,7 +41,7 @@ void demoClient() { // tag::documentation[] FineractClient fineract = FineractClient.builder().baseURL("https://demo.fineract.dev/fineract-provider/api/v1/").tenant("default") .basicAuth("mifos", "password").build(); - List staff = Calls.ok(fineract.staff.retrieveAll16(1L, true, false, "ACTIVE")); + List staff = Calls.ok(fineract.staff.retrieveAllStaff(1L, true, false, "ACTIVE")); String name = staff.get(0).getDisplayName(); log.info("Display name: {}", name); // end::documentation[] diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/COBConstant.java b/fineract-cob/src/main/java/org/apache/fineract/cob/COBConstant.java index 0e874850aac..8af61de5a03 100644 --- a/fineract-cob/src/main/java/org/apache/fineract/cob/COBConstant.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/COBConstant.java @@ -27,7 +27,11 @@ public class COBConstant { public static final String COB_CUSTOM_JOB_PARAMETER_KEY = "CUSTOM_JOB_PARAMETER_ID"; + public static final String INLINE_IDS_PARAMETER_NAME = "LoanIds"; + public static final String COB_PARAMETER = "loanCobParameter"; public static final Long NUMBER_OF_DAYS_BEHIND = 1L; + public static final String PARTITION_KEY = "partition"; + public static final String PARTITION_PREFIX = "partition_"; protected COBConstant() { diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/common/CommonPartitioner.java b/fineract-cob/src/main/java/org/apache/fineract/cob/common/CommonPartitioner.java new file mode 100644 index 00000000000..5d27f3973d7 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/common/CommonPartitioner.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.common; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.data.BusinessStepNameAndOrder; +import org.apache.fineract.cob.data.COBParameter; +import org.apache.fineract.cob.data.COBPartition; +import org.apache.fineract.cob.resolver.BusinessDateResolver; +import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.launch.JobExecutionNotRunningException; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.NoSuchJobExecutionException; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.util.StopWatch; + +@Slf4j +@RequiredArgsConstructor +public abstract class CommonPartitioner { + + private final JobOperator jobOperator; + private final StepExecution stepExecution; + private final Long numberOfDays; + private final RetrieveIdService retrieveIdService; + + public Map getPartitions(int partitionSize, Set cobBusinessSteps) { + if (cobBusinessSteps.isEmpty()) { + stopJobExecution(); + return Map.of(); + } + LocalDate businessDate = BusinessDateResolver.resolve(stepExecution); + boolean isCatchUp = CatchUpFlagResolver.resolve(stepExecution); + StopWatch sw = new StopWatch(); + sw.start(); + List partitions = new ArrayList<>( + retrieveIdService.retrieveLoanCOBPartitions(numberOfDays, businessDate, isCatchUp, partitionSize)); + sw.stop(); + // if there is no loan to be closed, we still would like to create at least one partition + + if (partitions.isEmpty()) { + partitions.add(new COBPartition(0L, 0L, 1L, 0L)); + } + log.info( + "{}} found {} loans to be processed as part of COB. {} partitions were created using partition size {}. RetrieveLoanCOBPartitions was executed in {} ms.", + getClass().getName(), getLoanCount(partitions), partitions.size(), partitionSize, sw.getTotalTimeMillis()); + return partitions.stream().collect(Collectors.toMap(l -> COBConstant.PARTITION_PREFIX + l.getPageNo(), + l -> createExecutionContextForPartition(cobBusinessSteps, l, businessDate, isCatchUp))); + } + + private long getLoanCount(List loanCOBPartitions) { + return loanCOBPartitions.stream().map(COBPartition::getCount).reduce(0L, Long::sum); + } + + private ExecutionContext createExecutionContextForPartition(Set cobBusinessSteps, + COBPartition loanCOBPartition, LocalDate businessDate, boolean isCatchUp) { + ExecutionContext executionContext = new ExecutionContext(); + executionContext.put(COBConstant.BUSINESS_STEPS, cobBusinessSteps); + executionContext.put(COBConstant.COB_PARAMETER, new COBParameter(loanCOBPartition.getMinId(), loanCOBPartition.getMaxId())); + executionContext.put(COBConstant.PARTITION_KEY, COBConstant.PARTITION_PREFIX + loanCOBPartition.getPageNo()); + executionContext.put(COBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDate.toString()); + executionContext.put(COBConstant.IS_CATCH_UP_PARAMETER_NAME, Boolean.toString(isCatchUp)); + return executionContext; + } + + private void stopJobExecution() { + Long jobId = stepExecution.getJobExecution().getId(); + try { + jobOperator.stop(jobId); + } catch (NoSuchJobExecutionException | JobExecutionNotRunningException e) { + log.error("There is no running execution for the given execution ID. Execution ID: {}", jobId); + throw new RuntimeException(e); + } + + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java b/fineract-cob/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java similarity index 96% rename from fineract-provider/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java index 70639d9fb4e..652d50bbee3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/converter/COBParameterConverter.java @@ -29,6 +29,7 @@ public static COBParameter convert(Object obj) { if (obj instanceof COBParameter) { return (COBParameter) obj; } else if (obj instanceof LoanCOBParameter loanCOBParameter) { + // for backward compatibility return loanCOBParameter.toCOBParameter(); } return null; diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/data/BusinessStepNameAndOrder.java b/fineract-cob/src/main/java/org/apache/fineract/cob/data/BusinessStepNameAndOrder.java index 5f084f458b0..ddad257e18b 100644 --- a/fineract-cob/src/main/java/org/apache/fineract/cob/data/BusinessStepNameAndOrder.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/data/BusinessStepNameAndOrder.java @@ -19,6 +19,10 @@ package org.apache.fineract.cob.data; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -31,4 +35,10 @@ public class BusinessStepNameAndOrder { private String stepName; private Long stepOrder; + + public static TreeMap getBusinessStepMap(Set businessSteps) { + Map businessStepMap = businessSteps.stream() + .collect(Collectors.toMap(BusinessStepNameAndOrder::getStepOrder, BusinessStepNameAndOrder::getStepName)); + return new TreeMap<>(businessStepMap); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java b/fineract-cob/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java similarity index 99% rename from fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java index a17c5a9bf75..6c9d7eb6d9a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/data/LoanCOBParameter.java @@ -29,6 +29,7 @@ @NoArgsConstructor @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @EqualsAndHashCode +@Deprecated public class LoanCOBParameter { private Long minLoanId; diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AbstractLockingService.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AbstractLockingService.java new file mode 100644 index 00000000000..3fdfab8c177 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AbstractLockingService.java @@ -0,0 +1,91 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import java.sql.PreparedStatement; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.springframework.jdbc.core.JdbcTemplate; + +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractLockingService implements LockingService { + + private final JdbcTemplate jdbcTemplate; + private final FineractProperties fineractProperties; + private final AccountLockRepository loanAccountLockRepository; + + protected abstract String getBatchLoanLockUpgrade(); + + protected abstract String getBatchLoanLockInsert(); + + @Override + public void upgradeLock(List accountsToLock, LockOwner lockOwner) { + jdbcTemplate.batchUpdate(getBatchLoanLockUpgrade(), accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> { + ps.setString(1, lockOwner.name()); + ps.setObject(2, DateUtils.getAuditOffsetDateTime()); + ps.setLong(3, id); + }); + } + + @Override + public List findAllByLoanIdIn(List loanIds) { + return loanAccountLockRepository.findAllByLoanIdIn(loanIds); + } + + @Override + public T findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner) { + return loanAccountLockRepository.findByLoanIdAndLockOwner(loanId, lockOwner).orElseGet(() -> { + log.warn("There is no lock for loan account with id: {}", loanId); + return null; + }); + } + + @Override + public List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner) { + return loanAccountLockRepository.findAllByLoanIdInAndLockOwner(loanIds, lockOwner); + } + + @Override + public void applyLock(List loanIds, LockOwner lockOwner) { + LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); + jdbcTemplate.batchUpdate(getBatchLoanLockInsert(), loanIds, loanIds.size(), (PreparedStatement ps, Long loanId) -> { + ps.setLong(1, loanId); + ps.setLong(2, 1); + ps.setString(3, lockOwner.name()); + ps.setObject(4, DateUtils.getAuditOffsetDateTime()); + ps.setObject(5, cobBusinessDate); + }); + } + + @Override + public void deleteByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner) { + loanAccountLockRepository.deleteByLoanIdInAndLockOwner(loanIds, lockOwner); + } + + private int getInClauseParameterSizeLimit() { + return fineractProperties.getQuery().getInClauseParameterSizeLimit(); + } +} diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLock.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLock.java new file mode 100644 index 00000000000..5721b33e9a3 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLock.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PostLoad; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Transient; +import jakarta.persistence.Version; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.springframework.data.domain.Persistable; + +@Getter +@MappedSuperclass +@NoArgsConstructor +public abstract class AccountLock implements Persistable, Serializable { + + protected static final long serialVersionUID = 2272591907035824317L; + + @Id + @Getter + @Column(name = "loan_id", nullable = false) + protected Long loanId; + + @Version + @Getter + @Column(name = "version") + protected Long version; + + @Enumerated(EnumType.STRING) + @Getter + @Column(name = "lock_owner", nullable = false) + protected LockOwner lockOwner; + + @Column(name = "lock_placed_on", nullable = false) + @Getter + protected OffsetDateTime lockPlacedOn; + + @Column(name = "error") + @Getter + protected String error; + + @Column(name = "stacktrace") + @Getter + protected String stacktrace; + + @Column(name = "lock_placed_on_cob_business_date") + @Getter + protected LocalDate lockPlacedOnCobBusinessDate; + + @Transient + @Setter(value = AccessLevel.NONE) + @Getter + protected boolean isNew = true; + + @PrePersist + @PostLoad + void markNotNew() { + this.isNew = false; + } + + @Override + public Long getId() { + return getLoanId(); + } + + public AccountLock(Long loanId, LockOwner lockOwner, LocalDate lockPlacedOnCobBusinessDate) { + this.loanId = loanId; + this.lockOwner = lockOwner; + this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); + this.lockPlacedOnCobBusinessDate = lockPlacedOnCobBusinessDate; + } + + public void setError(String errorMessage, String stacktrace) { + this.error = errorMessage; + this.stacktrace = stacktrace; + } + + public void setNewLockOwner(LockOwner newLockOwner) { + this.lockOwner = newLockOwner; + this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); + } +} diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLockRepository.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLockRepository.java new file mode 100644 index 00000000000..9d1a34dd7b4 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/AccountLockRepository.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface AccountLockRepository { + + Optional findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + + void deleteByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + + List findAllByLoanIdIn(List loanIds); + + boolean existsByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + + boolean existsByLoanIdAndLockOwnerAndErrorIsNotNull(Long loanId, LockOwner lockOwner); + + List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + + void removeByLockOwnerInAndErrorIsNotNullAndLockPlacedOnCobBusinessDateIsNotNull(List lockOwners); + + Page findAll(Pageable loanAccountLockPage); + + T saveAndFlush(T entity); + + Optional findById(Long id); +} diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepository.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepository.java index 765db8e28ca..ba5d8ed4f4a 100644 --- a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepository.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepository.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.cob.domain; -public interface CustomLoanAccountLockRepository { +public interface CustomLoanAccountLockRepository { void updateLoanFromAccountLocks(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LockOwner.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockOwner.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/cob/domain/LockOwner.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockOwner.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockingService.java similarity index 71% rename from fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockingService.java index 00830ba4e5f..7b2e12ec066 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingService.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/domain/LockingService.java @@ -16,23 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cob.loan; +package org.apache.fineract.cob.domain; import java.util.List; -import org.apache.fineract.cob.domain.LoanAccountLock; -import org.apache.fineract.cob.domain.LockOwner; -public interface LoanLockingService { +public interface LockingService { void upgradeLock(List accountsToLock, LockOwner lockOwner); void deleteByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); - List findAllByLoanIdIn(List loanIds); + List findAllByLoanIdIn(List loanIds); - LoanAccountLock findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + T findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); - List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); void applyLock(List loanIds, LockOwner lockOwner); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java b/fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockCannotBeAppliedException.java similarity index 85% rename from fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockCannotBeAppliedException.java index 5d5c6e1c89c..aac761ce49e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanLockCannotBeAppliedException.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockCannotBeAppliedException.java @@ -18,9 +18,9 @@ */ package org.apache.fineract.cob.exceptions; -public class LoanLockCannotBeAppliedException extends Exception { +public class LockCannotBeAppliedException extends Exception { - public LoanLockCannotBeAppliedException(String message, Throwable cause) { + public LockCannotBeAppliedException(String message, Throwable cause) { super(message, cause); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanReadException.java b/fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockedReadException.java similarity index 90% rename from fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanReadException.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockedReadException.java index 482f4f55b26..6a94b8c24a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanReadException.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/exceptions/LockedReadException.java @@ -18,11 +18,11 @@ */ package org.apache.fineract.cob.exceptions; -public class LoanReadException extends Exception { +public class LockedReadException extends Exception { private final Long id; - public LoanReadException(Long id, Throwable t) { + public LockedReadException(Long id, Throwable t) { super(String.format("Loan is in already locked state! loanId: %d", id), t); this.id = id; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java b/fineract-cob/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java similarity index 84% rename from fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java index e42c9973ca9..34e44f9292d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java @@ -23,13 +23,12 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.AccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.LoanReadException; -import org.apache.fineract.cob.loan.LoanLockingService; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.exceptions.LockedReadException; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.serialization.ThrowableSerialization; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.batch.core.annotation.OnProcessError; import org.springframework.batch.core.annotation.OnReadError; import org.springframework.batch.core.annotation.OnSkipInProcess; @@ -44,9 +43,9 @@ @Slf4j @RequiredArgsConstructor -public abstract class AbstractLoanItemListener { +public abstract class AbstractLoanItemListener> { - private final LoanLockingService loanLockingService; + private final LockingService loanLockingService; private final TransactionTemplate transactionTemplate; @@ -57,7 +56,7 @@ private void updateAccountLockWithError(List loanIds, String msg, Throwabl @Override protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { for (Long loanId : loanIds) { - LoanAccountLock loanAccountLock = loanLockingService.findByLoanIdAndLockOwner(loanId, getLockOwner()); + T loanAccountLock = loanLockingService.findByLoanIdAndLockOwner(loanId, getLockOwner()); if (loanAccountLock != null) { loanAccountLock.setError(String.format(msg, loanId), ThrowableSerialization.serialize(e)); } @@ -68,7 +67,7 @@ protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { @OnReadError public void onReadError(Exception e) { - if (e instanceof LoanReadException ee) { + if (e instanceof LockedReadException ee) { log.warn("Error was triggered during reading of Loan (id={}) due to: {}", ee.getId(), ThrowableSerialization.serialize(e)); updateAccountLockWithError(List.of(ee.getId()), "Loan (id: %d) reading is failed", e); } else { @@ -77,13 +76,13 @@ public void onReadError(Exception e) { } @OnProcessError - public void onProcessError(@NonNull Loan item, Exception e) { + public void onProcessError(@NonNull S item, Exception e) { log.warn("Error was triggered during processing of Loan (id={}) due to: {}", item.getId(), ThrowableSerialization.serialize(e)); updateAccountLockWithError(List.of(item.getId()), "Loan (id: %d) processing is failed", e); } @OnWriteError - public void onWriteError(Exception e, @NonNull Chunk items) { + public void onWriteError(Exception e, @NonNull Chunk items) { List loanIds = items.getItems().stream().map(AbstractPersistableCustom::getId).toList(); log.warn("Error was triggered during writing of Loans (ids={}) due to: {}", loanIds, ThrowableSerialization.serialize(e)); @@ -96,12 +95,12 @@ public void onSkipInRead(@NonNull Throwable e) { } @OnSkipInProcess - public void onSkipInProcess(@NonNull Loan item, @NonNull Throwable e) { + public void onSkipInProcess(@NonNull S item, @NonNull Throwable e) { log.warn("Skipping was triggered during processing of Loan (id={})", item.getId()); } @OnSkipInWrite - public void onSkipInWrite(@NonNull Loan item, @NonNull Throwable e) { + public void onSkipInWrite(@NonNull S item, @NonNull Throwable e) { log.warn("Skipping was triggered during writing of Loan (id={})", item.getId()); } diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/processor/AbstractItemProcessor.java b/fineract-cob/src/main/java/org/apache/fineract/cob/processor/AbstractItemProcessor.java new file mode 100644 index 00000000000..9efdeb1d7e9 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/processor/AbstractItemProcessor.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.processor; + +import static org.apache.fineract.cob.data.BusinessStepNameAndOrder.getBusinessStepMap; + +import java.time.LocalDate; +import java.util.Set; +import java.util.TreeMap; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.data.BusinessStepNameAndOrder; +import org.apache.fineract.cob.resolver.BusinessDateResolver; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.AfterStep; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.lang.NonNull; + +@RequiredArgsConstructor +public abstract class AbstractItemProcessor> implements ItemProcessor { + + private final COBBusinessStepService cobBusinessStepService; + + @Setter + private ExecutionContext executionContext; + + @Getter + private LocalDate businessDate; + + @SuppressWarnings({ "unchecked" }) + @Override + public I process(@NonNull I item) throws Exception { + Set businessSteps = (Set) executionContext.get("businessSteps"); + if (businessSteps == null) { + throw new IllegalStateException("No business steps found in the execution context"); + } + TreeMap businessStepMap = getBusinessStepMap(businessSteps); + + I alreadyProcessedLoan = cobBusinessStepService.run(businessStepMap, item); + setLastRun(alreadyProcessedLoan); + return alreadyProcessedLoan; + } + + protected void setBusinessDate(StepExecution stepExecution) { + businessDate = BusinessDateResolver.resolve(stepExecution); + } + + @AfterStep + public ExitStatus afterStep(@NonNull StepExecution stepExecution) { + return ExitStatus.COMPLETED; + } + + public abstract void setLastRun(I processedLoan); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java b/fineract-cob/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java similarity index 93% rename from fineract-provider/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java index e91dabbbc80..7b57f89407e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/resolver/BusinessDateResolver.java @@ -19,7 +19,7 @@ package org.apache.fineract.cob.resolver; import java.time.LocalDate; -import org.apache.fineract.cob.loan.LoanCOBConstant; +import org.apache.fineract.cob.COBConstant; import org.springframework.batch.core.StepExecution; public final class BusinessDateResolver { @@ -27,7 +27,7 @@ public final class BusinessDateResolver { private BusinessDateResolver() {} public static LocalDate resolve(StepExecution stepExecution) { - Object bd = stepExecution.getJobExecution().getExecutionContext().get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME); + Object bd = stepExecution.getJobExecution().getExecutionContext().get(COBConstant.BUSINESS_DATE_PARAMETER_NAME); return switch (bd) { case null -> throw new IllegalStateException( "Missing BusinessDate in JobExecutionContext for jobExecutionId=" + stepExecution.getJobExecution().getId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java b/fineract-cob/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java similarity index 92% rename from fineract-provider/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java index 25afa79c45e..6ddf59df585 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/resolver/CatchUpFlagResolver.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.cob.resolver; -import org.apache.fineract.cob.loan.LoanCOBConstant; +import org.apache.fineract.cob.COBConstant; import org.springframework.batch.core.StepExecution; public final class CatchUpFlagResolver { @@ -26,7 +26,7 @@ public final class CatchUpFlagResolver { private CatchUpFlagResolver() {} public static boolean resolve(StepExecution stepExecution) { - Object isCatchUp = stepExecution.getJobExecution().getExecutionContext().get(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME); + Object isCatchUp = stepExecution.getJobExecution().getExecutionContext().get(COBConstant.IS_CATCH_UP_PARAMETER_NAME); return switch (isCatchUp) { case null -> false; case String isCatchUpStr -> Boolean.parseBoolean(isCatchUpStr); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java b/fineract-cob/src/main/java/org/apache/fineract/cob/service/AbstractAccountLockService.java similarity index 71% rename from fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/service/AbstractAccountLockService.java index 1077c7afad0..29d5aa8a531 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/service/AbstractAccountLockService.java @@ -20,26 +20,26 @@ import java.util.List; import lombok.RequiredArgsConstructor; -import org.apache.fineract.cob.domain.LoanAccountLock; -import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.AccountLock; +import org.apache.fineract.cob.domain.AccountLockRepository; +import org.apache.fineract.cob.domain.CustomLoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -@Service @RequiredArgsConstructor -public class LoanAccountLockServiceImpl implements LoanAccountLockService { +public abstract class AbstractAccountLockService implements AccountLockService { - private final LoanAccountLockRepository loanAccountLockRepository; + private final AccountLockRepository loanAccountLockRepository; + private final CustomLoanAccountLockRepository customLoanAccountLockRepository; @Override - public List getLockedLoanAccountByPage(int page, int limit) { + public List getLockedLoanAccountByPage(int page, int limit) { Pageable loanAccountLockPage = PageRequest.of(page, limit); - Page loanAccountLocks = loanAccountLockRepository.findAll(loanAccountLockPage); + Page loanAccountLocks = loanAccountLockRepository.findAll(loanAccountLockPage); return loanAccountLocks.getContent(); } @@ -58,8 +58,9 @@ public boolean isLockOverrulable(Long loanId) { @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateCobAndRemoveLocks() { - loanAccountLockRepository.updateLoanFromAccountLocks(); - loanAccountLockRepository.removeLockByOwner(); + customLoanAccountLockRepository.updateLoanFromAccountLocks(); + loanAccountLockRepository.removeByLockOwnerInAndErrorIsNotNullAndLockPlacedOnCobBusinessDateIsNotNull( + List.of(LockOwner.LOAN_COB_CHUNK_PROCESSING, LockOwner.LOAN_INLINE_COB_PROCESSING)); } } diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/service/AccountLockService.java b/fineract-cob/src/main/java/org/apache/fineract/cob/service/AccountLockService.java new file mode 100644 index 00000000000..4f55670ac2f --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/service/AccountLockService.java @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import java.util.List; +import org.apache.fineract.cob.domain.AccountLock; + +public interface AccountLockService { + + List getLockedLoanAccountByPage(int page, int limit); + + boolean isLoanHardLocked(Long loanId); + + boolean isLockOverrulable(Long loanId); + + void updateCobAndRemoveLocks(); +} diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/service/BeforeStepLockingItemReaderHelper.java b/fineract-cob/src/main/java/org/apache/fineract/cob/service/BeforeStepLockingItemReaderHelper.java new file mode 100644 index 00000000000..c79d761829c --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/service/BeforeStepLockingItemReaderHelper.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.LinkedBlockingQueue; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.converter.COBParameterConverter; +import org.apache.fineract.cob.data.COBParameter; +import org.apache.fineract.cob.domain.AccountLock; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.lang.NonNull; + +@RequiredArgsConstructor +public class BeforeStepLockingItemReaderHelper { + + private final RetrieveIdService retrieveIdService; + private final LockingService loanLockingService; + + @SuppressWarnings({ "unchecked" }) + public LinkedBlockingQueue filterRemainingData(@NonNull StepExecution stepExecution) { + ExecutionContext executionContext = stepExecution.getExecutionContext(); + COBParameter loanCOBParameter = COBParameterConverter.convert(executionContext.get(COBConstant.COB_PARAMETER)); + List loanIds; + boolean isCatchUp = CatchUpFlagResolver.resolve(stepExecution); + if (Objects.isNull(loanCOBParameter) + || (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId())) + || (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) { + loanIds = Collections.emptyList(); + } else { + loanIds = retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, isCatchUp); + if (!loanIds.isEmpty()) { + List lockedByCOBChunkProcessingAccountIds = getLoanIdsLockedWithChunkProcessingLock(loanIds); + loanIds.retainAll(lockedByCOBChunkProcessingAccountIds); + } + } + return new LinkedBlockingQueue<>(loanIds); + } + + private List getLoanIdsLockedWithChunkProcessingLock(List loanIds) { + List accountLocks = new ArrayList<>( + loanLockingService.findAllByLoanIdInAndLockOwner(loanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING)); + return accountLocks.stream().map(T::getLoanId).toList(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java b/fineract-cob/src/main/java/org/apache/fineract/cob/service/RetrieveIdService.java similarity index 85% rename from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java rename to fineract-cob/src/main/java/org/apache/fineract/cob/service/RetrieveIdService.java index 590757fc741..a934a161c3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/service/RetrieveIdService.java @@ -16,8 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cob.loan; +package org.apache.fineract.cob.service; +import java.sql.ResultSet; +import java.sql.SQLException; import java.time.LocalDate; import java.util.List; import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo; @@ -26,7 +28,7 @@ import org.apache.fineract.cob.data.COBPartition; import org.springframework.data.repository.query.Param; -public interface RetrieveLoanIdService { +public interface RetrieveIdService { List retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize); @@ -41,4 +43,8 @@ public interface RetrieveLoanIdService { List findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate); List retrieveLoanBehindOnDisbursementDate(LocalDate businessDateByType, List loanIds); + + static COBPartition mapRow(ResultSet rs, int rowNum) throws SQLException { + return new COBPartition(rs.getLong("min"), rs.getLong("max"), rs.getLong("page"), rs.getLong("count")); + } } diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/tasklet/ApplyCommonLockTasklet.java b/fineract-cob/src/main/java/org/apache/fineract/cob/tasklet/ApplyCommonLockTasklet.java new file mode 100644 index 00000000000..b1fcd63d446 --- /dev/null +++ b/fineract-cob/src/main/java/org/apache/fineract/cob/tasklet/ApplyCommonLockTasklet.java @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.tasklet; + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; + +import com.google.common.collect.Lists; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.converter.COBParameterConverter; +import org.apache.fineract.cob.data.COBParameter; +import org.apache.fineract.cob.domain.AccountLock; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.exceptions.LockCannotBeAppliedException; +import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.lang.NonNull; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Slf4j +@RequiredArgsConstructor +public abstract class ApplyCommonLockTasklet implements Tasklet { + + private static final long NUMBER_OF_RETRIES = 3; + private final FineractProperties fineractProperties; + private final LockingService loanLockingService; + private final RetrieveIdService retrieveIdService; + private final TransactionTemplate transactionTemplate; + + public abstract String getCOBParameter(); + + public abstract LockOwner getLockOwner(); + + @Override + @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") + public RepeatStatus execute(@NonNull StepContribution contribution, @NonNull ChunkContext chunkContext) + throws LockCannotBeAppliedException { + ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext(); + long numberOfExecutions = contribution.getStepExecution().getCommitCount(); + COBParameter loanCOBParameter = COBParameterConverter.convert(executionContext.get(getCOBParameter())); + boolean isCatchUp = CatchUpFlagResolver.resolve(contribution.getStepExecution()); + List loanIds; + if (Objects.isNull(loanCOBParameter) + || (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId())) + || (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) { + loanIds = Collections.emptyList(); + } else { + loanIds = new ArrayList<>( + retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, isCatchUp)); + } + List> loanIdPartitions = Lists.partition(loanIds, getInClauseParameterSizeLimit()); + List accountLocks = new ArrayList<>(); + loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(loanLockingService.findAllByLoanIdIn(loanIdPartition))); + + List toBeProcessedLoanIds = new ArrayList<>(loanIds); + List alreadyLockedAccountIds = accountLocks.stream().map(AccountLock::getLoanId).toList(); + + toBeProcessedLoanIds.removeAll(alreadyLockedAccountIds); + try { + applyLocks(toBeProcessedLoanIds); + } catch (Exception e) { + if (numberOfExecutions > NUMBER_OF_RETRIES) { + String message = "There was an error applying lock to loan accounts."; + log.error("{}", message, e); + throw new LockCannotBeAppliedException(message, e); + } else { + return RepeatStatus.CONTINUABLE; + } + } + + return RepeatStatus.FINISHED; + } + + private void applyLocks(List toBeProcessedLoanIds) { + transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + + @Override + protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { + log.info("Apply locks for {} by owner {}", toBeProcessedLoanIds, getLockOwner()); + loanLockingService.applyLock(toBeProcessedLoanIds, getLockOwner()); + } + }); + } + + private int getInClauseParameterSizeLimit() { + return fineractProperties.getQuery().getInClauseParameterSizeLimit(); + } +} diff --git a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml index 509a60dd916..af0d95e2a35 100644 --- a/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml +++ b/fineract-cob/src/main/resources/jpa/static-weaving/module/fineract-cob/persistence.xml @@ -65,7 +65,7 @@ org.apache.fineract.infrastructure.businessdate.domain.BusinessDate org.apache.fineract.infrastructure.codes.domain.CodeValue - + false diff --git a/fineract-command/src/main/java/org/apache/fineract/command/starter/CommandPersistenceConfiguration.java b/fineract-command/src/main/java/org/apache/fineract/command/starter/CommandPersistenceConfiguration.java index 110908f75ad..535623c9121 100644 --- a/fineract-command/src/main/java/org/apache/fineract/command/starter/CommandPersistenceConfiguration.java +++ b/fineract-command/src/main/java/org/apache/fineract/command/starter/CommandPersistenceConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; @Configuration -@EnableJdbcRepositories(basePackages = { "org.apache.fineract.**.domain", "org.apache.fineract.**.persistence" }) +@EnableJdbcRepositories(basePackages = { "org.apache.fineract.**.domain" }) @ComponentScan("org.apache.fineract.command.persistence") class CommandPersistenceConfiguration { diff --git a/fineract-command/src/test/java/org/apache/fineract/command/CommandBaseTest.java b/fineract-command/src/test/java/org/apache/fineract/command/CommandBaseTest.java index 638a65ea6fa..79f266c5c0d 100644 --- a/fineract-command/src/test/java/org/apache/fineract/command/CommandBaseTest.java +++ b/fineract-command/src/test/java/org/apache/fineract/command/CommandBaseTest.java @@ -33,6 +33,7 @@ import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.utility.DockerImageName; @@ -45,12 +46,13 @@ abstract class CommandBaseTest { protected static Network network = Network.newNetwork(); @Container - private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer<>(DockerImageName.parse("postgres:16")) + private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer<>(DockerImageName.parse("postgres:18.3")) .withNetwork(network).withUsername("root").withPassword("mifos").withDatabaseName("fineract-test"); @Container - private static final MariaDBContainer MARIADB_CONTAINER = new MariaDBContainer<>(DockerImageName.parse("mariadb:11.4")) - .withNetwork(network).withUsername("root").withPassword("mifos").withDatabaseName("fineract-test"); + private static final MariaDBContainer MARIADB_CONTAINER = new MariaDBContainer<>(DockerImageName.parse("mariadb:12.2")) + .withNetwork(network).withUsername("root").withPassword("mifos").withDatabaseName("fineract-test") + .withCommand("--innodb-snapshot-isolation=OFF").waitingFor(Wait.forListeningPort()); @Container private static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer<>(DockerImageName.parse("mysql:8")).withNetwork(network) diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index 01e6b6d2513..914d2014f44 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -149,6 +149,8 @@ private static void init() { commandStrategies.put(CommandContext .resource("v1\\/savingsaccounts\\/" + NUMBER_REGEX + "\\/transactions\\/" + NUMBER_REGEX + OPTIONAL_COMMAND_PARAM_REGEX) .method(POST).build(), "savingsAccountAdjustTransactionCommandStrategy"); + commandStrategies.put(CommandContext.resource("v1\\/savingsaccounts\\/" + NUMBER_REGEX + "\\/charges").method(POST).build(), + "createSavingsAccountChargeCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/charges").method(POST).build(), "createChargeCommandStrategy"); commandStrategies.put( diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 61b339618b4..8493553fb56 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -426,13 +426,6 @@ public CommandWrapperBuilder deleteReport(final Long id) { return this; } - public CommandWrapperBuilder updateCurrencies() { - this.actionName = "UPDATE"; - this.entityName = "CURRENCY"; - this.href = "/currencies"; - return this; - } - public CommandWrapperBuilder createSms() { this.actionName = "CREATE"; this.entityName = "SMS"; @@ -553,6 +546,30 @@ public CommandWrapperBuilder updateLoanProduct(final Long productId) { return this; } + public CommandWrapperBuilder createWorkingCapitalLoanProduct() { + this.actionName = "CREATE"; + this.entityName = "WORKINGCAPITALLOANPRODUCT"; + this.entityId = null; + this.href = "/working-capital-loan-products/template"; + return this; + } + + public CommandWrapperBuilder updateWorkingCapitalLoanProduct(final Long productId) { + this.actionName = "UPDATE"; + this.entityName = "WORKINGCAPITALLOANPRODUCT"; + this.entityId = productId; + this.href = "/working-capital-loan-products/" + productId; + return this; + } + + public CommandWrapperBuilder deleteWorkingCapitalLoanProduct(final Long productId) { + this.actionName = "DELETE"; + this.entityName = "WORKINGCAPITALLOANPRODUCT"; + this.entityId = productId; + this.href = "/working-capital-loan-products/" + productId; + return this; + } + public CommandWrapperBuilder createClientIdentifier(final Long clientId) { this.actionName = "CREATE"; this.entityName = "CLIENTIDENTIFIER"; @@ -1862,55 +1879,6 @@ public CommandWrapperBuilder deleteCalendar(final String supportedEntityType, fi return this; } - public CommandWrapperBuilder createNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId) { - this.actionName = "CREATE"; - this.entityName = resourceDetails.entityName();// Note supports multiple - // resources. Note - // Permissions are set - // for each resource. - this.clientId = resourceDetails.getClientId(); - this.loanId = resourceDetails.getLoanId(); - this.savingsId = resourceDetails.getSavingsId(); - this.groupId = resourceDetails.getGroupId(); - this.subentityId = resourceDetails.subresourceId(); - this.href = "/" + resourceType + "/" + resourceId + "/notes/template"; - return this; - } - - public CommandWrapperBuilder updateNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId, - final Long noteId) { - this.actionName = "UPDATE"; - this.entityName = resourceDetails.entityName();// Note supports multiple - // resources. Note - // Permissions are set - // for each resource. - this.entityId = noteId; - this.clientId = resourceDetails.getClientId(); - this.loanId = resourceDetails.getLoanId(); - this.savingsId = resourceDetails.getSavingsId(); - this.groupId = resourceDetails.getGroupId(); - this.subentityId = resourceDetails.subresourceId(); - this.href = "/" + resourceType + "/" + resourceId + "/notes"; - return this; - } - - public CommandWrapperBuilder deleteNote(final CommandWrapper resourceDetails, final String resourceType, final Long resourceId, - final Long noteId) { - this.actionName = "DELETE"; - this.entityName = resourceDetails.entityName();// Note supports multiple - // resources. Note - // Permissions are set - // for each resource. - this.entityId = noteId; - this.clientId = resourceDetails.getClientId(); - this.loanId = resourceDetails.getLoanId(); - this.savingsId = resourceDetails.getSavingsId(); - this.groupId = resourceDetails.getGroupId(); - this.subentityId = resourceDetails.subresourceId(); - this.href = "/" + resourceType + "/" + resourceId + "/calendars/" + noteId; - return this; - } - public CommandWrapperBuilder createGroup() { this.actionName = "CREATE"; this.entityName = "GROUP"; @@ -2193,14 +2161,6 @@ public CommandWrapperBuilder deleteAccountingRule(final Long accountingRuleId) { return this; } - public CommandWrapperBuilder updateTaxonomyMapping(final Long mappingId) { - this.actionName = "UPDATE"; - this.entityName = "XBRLMAPPING"; - this.entityId = mappingId; - this.href = "/xbrlmapping"; - return this; - } - public CommandWrapperBuilder createHoliday() { this.actionName = "CREATE"; this.entityName = "HOLIDAY"; @@ -2351,6 +2311,14 @@ public CommandWrapperBuilder updateJobDetail(final Long jobId) { return this; } + public CommandWrapperBuilder executeSchedulerJob(final Long jobId) { + this.actionName = "EXECUTEJOB"; + this.entityName = "SCHEDULER"; + this.entityId = jobId; + this.href = "/jobs/" + jobId + "?command=executeJob"; + return this; + } + public CommandWrapperBuilder createMeeting(final CommandWrapper resourceDetails, final String supportedEntityType, final Long supportedEntityId) { this.actionName = "CREATE"; @@ -2387,13 +2355,6 @@ public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final S return this; } - public CommandWrapperBuilder updateCache() { - this.actionName = "UPDATE"; - this.entityName = "CACHE"; - this.href = "/cache"; - return this; - } - /** * Deposit account mappings */ @@ -3582,13 +3543,6 @@ public CommandWrapperBuilder updateRate(final Long rateId) { return this; } - public CommandWrapperBuilder updateBusinessDate() { - this.actionName = "UPDATE"; - this.entityName = "BUSINESS_DATE"; - this.href = "/businessdate"; - return this; - } - public CommandWrapperBuilder createDelinquencyRange() { this.actionName = "CREATE"; this.entityName = "DELINQUENCY_RANGE"; @@ -3651,13 +3605,6 @@ public CommandWrapperBuilder executeInlineJob(String jobName) { return this; } - public CommandWrapperBuilder updateExternalEventConfigurations() { - this.actionName = "UPDATE"; - this.entityName = "EXTERNAL_EVENT_CONFIGURATION"; - this.href = "/externaleventconfiguration"; - return this; - } - public CommandWrapperBuilder chargeOff(final Long loanId) { this.actionName = "CHARGEOFF"; this.entityName = "LOAN"; @@ -3954,4 +3901,13 @@ public CommandWrapperBuilder detachLoanOriginator(final Long loanId, final Long this.href = "/loans/" + loanId + "/originators/" + originatorId; return this; } + + public CommandWrapperBuilder savingsAccountForceWithdrawal(final Long accountId) { + this.actionName = "FORCE_WITHDRAWAL"; + this.entityName = "SAVINGSACCOUNT"; + this.entityId = accountId; + this.savingsId = accountId; + this.href = "/savingsaccounts/" + accountId; + return this; + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index 5406ac5b445..604a07a7a4d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -19,6 +19,7 @@ package org.apache.fineract.commands.service; import com.google.gson.JsonElement; +import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -32,6 +33,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.dataqueries.service.CleanupService; import org.apache.fineract.infrastructure.jobs.service.SchedulerJobRunnerReadService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.useradministration.domain.AppUser; @@ -49,6 +51,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio private final CommandProcessingService processAndLogCommandService; private final SchedulerJobRunnerReadService schedulerJobRunnerReadService; private final ConfigurationDomainService configurationService; + private final List cleanupServices; @Override public CommandProcessingResult logCommandSource(final CommandWrapper wrapper) { @@ -146,6 +149,11 @@ public Long rejectEntry(final Long makerCheckerId) { final AppUser maker = this.context.authenticatedUser(); commandSourceInput.markAsRejected(maker); this.commandSourceRepository.save(commandSourceInput); + if (cleanupServices != null) { + for (CleanupService cleanupService : cleanupServices) { + cleanupService.cleanup(commandSourceInput); + } + } return makerCheckerId; } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java index 88ec4bc0dde..edaa38bc970 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationConstants.java @@ -81,6 +81,9 @@ public final class GlobalConfigurationConstants { public static final String ALLOWED_LOAN_STATUSES_OF_DELAYED_SETTLEMENT_FOR_EXTERNAL_ASSET_TRANSFER = "allowed-loan-statuses-of-delayed-settlement-for-external-asset-transfer"; public static final String ENABLE_ORIGINATOR_CREATION_DURING_LOAN_APPLICATION = "enable-originator-creation-during-loan-application"; public static final String PASSWORD_REUSE_CHECK_HISTORY_COUNT = "password-reuse-check-history-count"; + public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT = "allow-force-withdrawal-on-savings-account"; + public static final String FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT = "force-withdrawal-on-savings-account-limit"; + public static final String FORCE_PASSWORD_RESET_ON_FIRST_LOGIN = "force-password-reset-on-first-login"; private GlobalConfigurationConstants() {} } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 83c21afd2b1..2f88120cf2f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -152,5 +152,11 @@ public interface ConfigurationDomainService { String getAssetOwnerTransferOustandingInterestStrategy(); + boolean isForceWithdrawalOnSavingsAccountEnabled(); + + Long retrieveForceWithdrawalOnSavingsAccountLimit(); + Integer getPasswordReuseRestrictionCount(); + + boolean isForcePasswordResetOnFirstLoginEnabled(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ThrowableSerialization.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/ThrowableSerialization.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/ThrowableSerialization.java rename to fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/ThrowableSerialization.java diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/CleanupService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/CleanupService.java new file mode 100644 index 00000000000..175a3a18b27 --- /dev/null +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/CleanupService.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.dataqueries.service; + +import org.apache.fineract.commands.domain.CommandSource; + +public interface CleanupService { + + void cleanup(CommandSource commandSource); + +} diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java index 16f9ad9c29e..51a501599e9 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java @@ -60,6 +60,7 @@ public enum JobName { ACCRUAL_ACTIVITY_POSTING("Accrual Activity Posting"), // ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"), // JOURNAL_ENTRY_AGGREGATION("Journal Entry Aggregation"), // + WORKING_CAPITAL_LOAN_COB_JOB("Working Capital Loan COB"), // ; // private final String name; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/PropertyService.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/springbatch/PropertyService.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/PropertyService.java rename to fineract-core/src/main/java/org/apache/fineract/infrastructure/springbatch/PropertyService.java diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java index 8fbe7125b58..b55b591c73c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDays.java @@ -21,16 +21,21 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; -import java.util.LinkedHashMap; -import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -import org.apache.fineract.infrastructure.core.api.JsonCommand; +import lombok.experimental.FieldNameConstants; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; +@Builder @Getter +@Setter @Entity +@NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants @Table(name = "m_working_days") public class WorkingDays extends AbstractPersistableCustom { @@ -47,51 +52,4 @@ public class WorkingDays extends AbstractPersistableCustom { @Column(name = "extend_term_holiday_repayment", nullable = false) private Boolean extendTermForRepaymentsOnHolidays; - protected WorkingDays() { - - } - - public WorkingDays(final String recurrence, final Integer repaymentReschedulingType, final Boolean extendTermForDailyRepayments, - final Boolean extendTermForRepaymentsOnHolidays) { - this.recurrence = recurrence; - this.repaymentReschedulingType = repaymentReschedulingType; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public Map update(final JsonCommand command) { - final Map actualChanges = new LinkedHashMap<>(7); - - final String recurrenceParamName = "recurrence"; - if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) { - final String newValue = command.stringValueOfParameterNamed(recurrenceParamName); - actualChanges.put(recurrenceParamName, newValue); - this.recurrence = newValue; - } - - final String repaymentRescheduleTypeParamName = "repaymentRescheduleType"; - if (command.isChangeInIntegerParameterNamed(repaymentRescheduleTypeParamName, this.repaymentReschedulingType)) { - final Integer newValue = command.integerValueOfParameterNamed(repaymentRescheduleTypeParamName); - actualChanges.put(repaymentRescheduleTypeParamName, WorkingDaysEnumerations.workingDaysStatusType(newValue)); - this.repaymentReschedulingType = RepaymentRescheduleType.fromInt(newValue).getValue(); - } - - if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments, - this.extendTermForDailyRepayments)) { - final Boolean newValue = command.booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForDailyRepayments); - actualChanges.put(WorkingDaysApiConstants.extendTermForDailyRepayments, newValue); - this.extendTermForDailyRepayments = newValue; - } - - if (command.isChangeInBooleanParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, - this.extendTermForRepaymentsOnHolidays)) { - final Boolean newValue = command - .booleanPrimitiveValueOfParameterNamed(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays); - actualChanges.put(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays, newValue); - this.extendTermForRepaymentsOnHolidays = newValue; - } - - return actualChanges; - } - } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java index e3f9606285a..c2caf644841 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/workingdays/domain/WorkingDaysEnumerations.java @@ -41,8 +41,8 @@ public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleTy case SAME_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.SAME_DAY.getValue().longValue(), RepaymentRescheduleType.SAME_DAY.getCode(), "same day"); - break; + case MOVE_TO_NEXT_WORKING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getCode(), "move to next working day"); @@ -52,10 +52,12 @@ public static EnumOptionData repaymentRescheduleType(final RepaymentRescheduleTy optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY.getCode(), "move to next repayment meeting day"); break; + case MOVE_TO_PREVIOUS_WORKING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_PREVIOUS_WORKING_DAY.getCode(), "move to previous working day"); break; + case MOVE_TO_NEXT_MEETING_DAY: optionData = new EnumOptionData(RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getValue().longValue(), RepaymentRescheduleType.MOVE_TO_NEXT_MEETING_DAY.getCode(), "move to next meeting day"); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java index 3a81a147c10..5b6f1a69120 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/account/PortfolioAccountType.java @@ -58,14 +58,4 @@ public static PortfolioAccountType fromInt(final Integer type) { } return enumType; } - - // TODO: bad practice and unnecessary code! why not just use the enum values themselves!?! - public boolean isSavingsAccount() { - return this.equals(SAVINGS); - } - - // TODO: bad practice and unnecessary code! why not just use the enum values themselves!?! - public boolean isLoanAccount() { - return this.equals(LOAN); - } } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java index eb791ef3aeb..081a0081855 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/Fund.java @@ -24,6 +24,7 @@ import jakarta.persistence.UniqueConstraint; import java.util.LinkedHashMap; import java.util.Map; +import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; @@ -33,6 +34,7 @@ @UniqueConstraint(columnNames = { "external_id" }, name = "fund_externalid_org") }) public class Fund extends AbstractPersistableCustom { + @Getter @Column(name = "name") private String name; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java rename to fineract-core/src/main/java/org/apache/fineract/portfolio/fund/domain/FundRepository.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java rename to fineract-core/src/main/java/org/apache/fineract/portfolio/fund/exception/FundNotFoundException.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java rename to fineract-core/src/main/java/org/apache/fineract/portfolio/fund/service/FundReadPlatformService.java diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java index 4be438b08ea..a0d800c8d8c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/paymenttype/api/PaymentTypeApiResource.java @@ -112,7 +112,7 @@ public PaymentTypeUpdateResponse updatePaymentType(@PathParam("paymentTypeId") f @Path("{paymentTypeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Payment Type", description = "Deletes payment type") + @Operation(summary = "Delete a Payment Type", operationId = "deleteCodePaymentType", description = "Deletes payment type") public PaymentTypeDeleteResponse deleteCode(@PathParam("paymentTypeId") final Long paymentTypeId) { final var command = new PaymentTypeDeleteCommand(); diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java index e0391a3ae8c..0d31101e5a8 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java @@ -57,6 +57,9 @@ public List determineInterestPostingPeriods(final LocalDate s return postingPeriods; } + if (postInterestAsOn == null) { + postInterestAsOn = Collections.emptyList(); + } LocalDate periodStartDate = startInterestCalculationLocalDate; LocalDate periodEndDate = periodStartDate; LocalDate actualPeriodStartDate = periodStartDate; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java index 7f4cab97be5..abbf4f7196a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java @@ -202,6 +202,15 @@ public static SavingsAccountTransactionEnumData transactionType(final SavingsAcc return optionData; } + public static EnumOptionData status(final SavingsAccountStatusEnumData status) { + + Long id = status.getId(); + String code = status.getCode(); + String value = status.getValue(); + + return new EnumOptionData(id, code, value); + } + public static SavingsAccountStatusEnumData status(final Integer statusEnum) { return status(SavingsAccountStatusType.fromInt(statusEnum)); } diff --git a/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java b/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java index 661564d52ef..5d7ab23054f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java +++ b/fineract-core/src/main/java/org/apache/fineract/useradministration/domain/AppUser.java @@ -131,6 +131,17 @@ public class AppUser extends AbstractPersistableCustom implements Platform @Column(name = "cannot_change_password", nullable = true) private Boolean cannotChangePassword; + @Column(name = "password_reset_required", nullable = false) + private boolean passwordResetRequired; + + public boolean isPasswordResetRequired() { + return this.passwordResetRequired; + } + + public void updatePasswordResetRequired(final boolean required) { + this.passwordResetRequired = required; + } + public static AppUser fromJson(final Office userOffice, final Staff linkedStaff, final Set allRoles, final Collection clients, final JsonCommand command) { diff --git a/fineract-doc/src/docs/en/chapters/architecture/api-backward-compatibility.adoc b/fineract-doc/src/docs/en/chapters/architecture/api-backward-compatibility.adoc new file mode 100644 index 00000000000..59eb81c4f24 --- /dev/null +++ b/fineract-doc/src/docs/en/chapters/architecture/api-backward-compatibility.adoc @@ -0,0 +1,190 @@ += API Backward Compatibility + +== Overview + +Apache Fineract enforces API backward compatibility using https://github.com/docktape/swagger-brake[swagger-brake], an automated tool that compares OpenAPI specifications between the base branch and a pull request to detect breaking changes. This ensures that existing API consumers are not broken when new changes are deployed. + +The check runs automatically on every pull request via the `verify-api-backward-compatibility.yml` GitHub Actions workflow. + +== How It Works + +The workflow follows these steps: + +. **Generate baseline spec** — Checks out the base branch (e.g. `develop`) and runs `./gradlew :fineract-provider:resolve` to generate the current OpenAPI specification. +. **Generate PR spec** — Checks out the PR branch and generates its OpenAPI specification. +. **Sanitize specs** — Patches known issues in the generated specs (e.g. missing `schema` entries in `requestBody` content) to prevent false positives. +. **Compare** — Runs `checkBreakingChanges` via the swagger-brake Gradle plugin to compare old vs new specs. +. **Report** — If breaking changes are found: +** A deduplicated summary table is written to the GitHub Actions Step Summary (visible on the workflow run page). +** A comment is posted on the PR (when token permissions allow). +** The full JSON report is archived as a build artifact. +** The workflow **fails**, blocking the PR. + +== Breaking Change Rules + +swagger-brake detects the following categories of breaking changes: + +=== Endpoint Rules + +[cols="1,3", options="header"] +|=== +| Rule | Description +| R001 | A stable (non-beta) API was changed to beta +| R002 | An API path was deleted +|=== + +=== Request Rules + +[cols="1,3", options="header"] +|=== +| Rule | Description +| R003 | A request media type (content type) was removed +| R004 | A request parameter was deleted +| R005 | An enum value was removed from a request parameter +| R006 | A parameter location changed (e.g. `query` to `header`) +| R007 | A parameter was made required +| R008 | A parameter type was changed +| R009 | An attribute was removed from a request body schema +| R010 | A property type was changed in a request schema +| R011 | An enum value was removed from a request body schema +|=== + +=== Response Rules + +[cols="1,3", options="header"] +|=== +| Rule | Description +| R012 | A response code was deleted +| R013 | A response media type was removed +| R014 | An attribute was removed from a response schema +| R015 | A property type was changed in a response schema +| R016 | An enum value was removed from a response schema +|=== + +=== Constraint Rules + +[cols="1,3", options="header"] +|=== +| Rule | Description +| R017 | A request parameter constraint was tightened (covers `maxLength`, `minLength`, `maximum`, `minimum`, `maxItems`, `minItems`, `uniqueItems`) +|=== + +== Gradle Configuration + +The swagger-brake plugin is configured in `fineract-provider/build.gradle`: + +[source,groovy] +---- +apply plugin: 'com.docktape.swagger-brake' + +swaggerBrake { + newApi = "${project.buildDir}/resources/main/static/fineract.json" + oldApi = findProperty('apiBaseline') ?: "${projectDir}/config/swagger/fineract-baseline.json" + outputFormats = ['JSON'] + outputFilePath = "${project.buildDir}/swagger-brake" + deprecatedApiDeletionAllowed = true + strictValidation = false +} +---- + +=== Configuration Options + +[cols="2,1,4", options="header"] +|=== +| Option | Default | Description +| `newApi` | — | Path to the new (PR branch) OpenAPI spec. Generated by the `resolve` task. +| `oldApi` | — | Path to the baseline OpenAPI spec. Provided via `-PapiBaseline` in CI, or falls back to a local file. +| `outputFormats` | `['STDOUT', 'HTML']` | Report formats. We use `['JSON']` to avoid STDOUT spam and parse the report programmatically. +| `outputFilePath` | `build/swagger-brake` | Directory for generated reports. +| `deprecatedApiDeletionAllowed` | `true` | When `true`, removing a deprecated endpoint is NOT a breaking change. +| `strictValidation` | `true` | When `false`, schemas without an explicit `type` field log a warning instead of failing. Set to `false` for Fineract because the generated spec has many type-less schemas. +| `excludedPaths` | `[]` | List of path prefixes to skip (e.g. `['/v1/smscampaigns', '/v1/internal']`). Useful for excluding endpoints undergoing cleanup. +| `ignoredBreakingChangeRules` | `[]` | List of rule codes to suppress entirely (e.g. `['R001']`). +| `betaApiExtensionName` | `x-beta-api` | Vendor extension name for marking beta APIs. Beta endpoints can be freely modified without triggering violations. +| `maxLogSerializationDepth` | `3` | Controls nested object serialization depth in logs (range 1-20). Increase if you see `StackOverflowError` from circular schema references. +|=== + +== Running Locally + +To run the check locally, you need a baseline spec to compare against: + +[source,bash] +---- +# 1. Generate the baseline from develop +git stash +git checkout develop +./gradlew :fineract-provider:resolve --no-daemon +cp fineract-provider/build/resources/main/static/fineract.json /tmp/baseline.json +git checkout - +git stash pop + +# 2. Generate your current spec and compare +./gradlew :fineract-provider:checkBreakingChanges \ + -PapiBaseline="/tmp/baseline.json" \ + --no-daemon +---- + +The JSON report is written to `fineract-provider/build/swagger-brake/`. + +== Handling Breaking Changes + +=== Intentional Breaking Changes + +If your PR intentionally introduces a breaking API change (e.g. removing a deprecated field): + +. The workflow will fail and report the violations. +. Document the breaking change in the PR description with justification. +. A committer will review and approve the PR with the understanding that the API contract is changing. + +=== Excluding Paths Under Cleanup + +If you need to fix incorrect API annotations on endpoints that are not yet stable, use `excludedPaths` to temporarily exclude them from checking: + +[source,groovy] +---- +swaggerBrake { + excludedPaths = [ + '/v1/smscampaigns', + '/v1/email', + ] +} +---- + +Path exclusion is **prefix-based** — excluding `/v1/smscampaigns` will skip all paths starting with that prefix. + +Remove the exclusion once the cleanup is complete. + +=== Marking Endpoints as Beta + +For endpoints that are experimental or under active development, mark them as beta in the Java code: + +[source,java] +---- +@Operation( + summary = "...", + extensions = @Extension( + properties = @ExtensionProperty(name = "x-beta-api", value = "true") + ) +) +---- + +Beta endpoints can be freely modified, created, or removed without triggering violations. Promoting a beta endpoint to stable (removing the extension) is also non-breaking. However, demoting a stable endpoint to beta **is** a breaking change (R001). + +== Report Format + +When breaking changes are detected, the workflow produces a deduplicated summary table: + +[cols="1,2,1,3,1", options="header"] +|=== +| Rule | Description | Detail | Affected endpoints | Count +| R014 | Response attribute removed | `totalOverpaid` | `GET /v1/loans`, `GET /v1/loans/{loanId}`, `GET /v1/loans/external-id/{loanExternalId}` | 3 +|=== + +The deduplication groups violations by rule code and affected attribute, collapsing multiple endpoint occurrences into a single row. This is important because a single schema change (e.g. removing a field from a shared response type) can generate dozens of raw violations — one per endpoint that uses that schema. + +== Tool Reference + +* **Tool**: https://github.com/docktape/swagger-brake[swagger-brake] v2.7.0 +* **Gradle plugin**: `com.docktape.swagger-brake` +* **Documentation**: https://docktape.github.io/swagger-brake/ +* **License**: Apache 2.0 diff --git a/fineract-doc/src/docs/en/chapters/architecture/index.adoc b/fineract-doc/src/docs/en/chapters/architecture/index.adoc index 01a635c6f91..212f8b452f5 100644 --- a/fineract-doc/src/docs/en/chapters/architecture/index.adoc +++ b/fineract-doc/src/docs/en/chapters/architecture/index.adoc @@ -35,3 +35,5 @@ include::business-date.adoc[leveloffset=+1] include::reliable-event-framework.adoc[leveloffset=+1] include::advanced-payment-allocation.adoc[leveloffset=+1] + +include::api-backward-compatibility.adoc[leveloffset=+1] diff --git a/fineract-doc/src/docs/en/chapters/features/index.adoc b/fineract-doc/src/docs/en/chapters/features/index.adoc index d718f19cbf4..5c386f1e1e7 100644 --- a/fineract-doc/src/docs/en/chapters/features/index.adoc +++ b/fineract-doc/src/docs/en/chapters/features/index.adoc @@ -15,3 +15,4 @@ include::pause-delinquency.adoc[leveloffset=+1] include::re-amortization.adoc[leveloffset=+1] include::re-ageing.adoc[leveloffset=+1] include::delayed-schedule-captures.adoc[leveloffset=+1] +include::loan-origination-details.adoc[leveloffset=+1] diff --git a/fineract-doc/src/docs/en/chapters/features/loan-origination-details.adoc b/fineract-doc/src/docs/en/chapters/features/loan-origination-details.adoc new file mode 100644 index 00000000000..81c0d310588 --- /dev/null +++ b/fineract-doc/src/docs/en/chapters/features/loan-origination-details.adoc @@ -0,0 +1,494 @@ += Loan Origination Details + +== Overview + +Tracks the originator of a loan (merchant, broker, affiliate, platform, or channel) for revenue sharing and reporting. Originator details are propagated through business events and reporting. + +== Configuration + +=== Enabling the Module + +[source,properties] +---- +fineract.module.loan-origination.enabled=${FINERACT_MODULE_LOAN_ORIGINATION_ENABLED:true} +---- + +**Enabled by default.** When disabled, API endpoints become unavailable, event enrichment is skipped, and the loan creation flow continues to work without originator processing. + +=== Global Configuration + +[cols="1,1,1", options="header"] +|=== +| Configuration Key | Default | Description + +| `enable-originator-creation-during-loan-application` +| `false` +| Allows automatic creation of new originator records when an unknown `externalId` is provided during loan creation +|=== + +== Data Model + +=== Originator Registry (`m_loan_originator`) + +[cols="1,1,1,1", options="header"] +|=== +| Column | Type | Required | Description + +| `id` +| BIGINT (PK) +| Yes +| Auto-generated primary key + +| `external_id` +| VARCHAR(100) +| Yes +| Unique, immutable external identifier (a.k.a. Revenue Share ID) + +| `name` +| VARCHAR(255) +| No +| Originator display name + +| `status` +| VARCHAR(20) +| Yes +| `ACTIVE`, `PENDING`, or `INACTIVE` + +| `originator_type_cv_id` +| INT (FK) +| No +| Code value reference to `LoanOriginatorType` + +| `channel_type_cv_id` +| INT (FK) +| No +| Code value reference to `LoanOriginationChannelType` + +| `created_on_utc` +| DATETIME +| Yes +| Record creation timestamp (UTC) + +| `created_by` +| BIGINT (FK) +| Yes +| Foreign key to `m_appuser` -- user who created the record + +| `last_modified_on_utc` +| DATETIME +| Yes +| Last modification timestamp (UTC) + +| `last_modified_by` +| BIGINT (FK) +| Yes +| Foreign key to `m_appuser` -- user who last modified the record +|=== + +=== Loan-Originator Mapping (`m_loan_originator_mapping`) + +Associates loans with originators. Supports multiple originators per loan, though typical usage is one. + +[cols="1,1,1,1", options="header"] +|=== +| Column | Type | Required | Description + +| `id` +| BIGINT (PK) +| Yes +| Auto-generated primary key + +| `loan_id` +| BIGINT (FK) +| Yes +| Foreign key to `m_loan` + +| `originator_id` +| BIGINT (FK) +| Yes +| Foreign key to `m_loan_originator` + +| `created_on_utc` +| DATETIME +| Yes +| Record creation timestamp (UTC) + +| `created_by` +| BIGINT (FK) +| Yes +| Foreign key to `m_appuser` -- user who created the record + +| `last_modified_on_utc` +| DATETIME +| Yes +| Last modification timestamp (UTC) + +| `last_modified_by` +| BIGINT (FK) +| Yes +| Foreign key to `m_appuser` -- user who last modified the record +|=== + +A unique constraint on `(loan_id, originator_id)` prevents duplicate assignments. + +=== Code Values + +**`LoanOriginatorType`** (default values): `MERCHANT`, `BROKER`, `AFFILIATE`, `PLATFORM` + +**`LoanOriginationChannelType`** (default values): `ONLINE`, `IN_STORE`, `API`, `AGGREGATOR` + +Both code value sets are extensible -- additional values can be added via the standard Code Values API. + +== API Endpoints + +=== Originator Registry APIs + +==== Create a Loan Originator + +`POST /v1/loan-originators` + +**Permission**: `CREATE_LOAN_ORIGINATOR` + +[source,json] +---- +{ + "name": "Best Merchant in US", + "externalId": "best-merchant-us-east", + "status": "ACTIVE", + "originatorTypeId": 12, + "channelTypeId": 44 +} +---- + +* `externalId` -- **required**, unique, max 100 characters +* `name` -- optional, max 255 characters +* `status` -- optional, defaults to `ACTIVE`. Allowed values: `ACTIVE`, `PENDING`, `INACTIVE` +* `originatorTypeId` -- optional, must reference a valid `LoanOriginatorType` code value +* `channelTypeId` -- optional, must reference a valid `LoanOriginationChannelType` code value + +**Response:** + +[source,json] +---- +{ + "resourceId": 13, + "resourceExternalId": "best-merchant-us-east" +} +---- + +|=== +| HTTP Code | Description + +| 200 | Created successfully +| 400 | Required parameter missing or incorrect format +| 403 | Duplicate external ID or insufficient permissions +| 404 | Originator type or channel type does not exist +|=== + +==== List All Loan Originators + +`GET /v1/loan-originators` + +**Permission**: `READ_LOAN_ORIGINATOR` + +[source,json] +---- +[ + { + "id": 13, + "externalId": "best-merchant-us-east", + "name": "Best Merchant in US", + "status": "ACTIVE", + "originatorType": { + "id": 12, + "name": "MERCHANT", + "active": true, + "mandatory": false + }, + "channelType": { + "id": 44, + "name": "ONLINE", + "active": true, + "mandatory": false + } + } +] +---- + +==== Retrieve a Loan Originator + +`GET /v1/loan-originators/{originatorId}` + +`GET /v1/loan-originators/external-id/{externalId}` + +**Permission**: `READ_LOAN_ORIGINATOR` + +==== Get Template Data + +`GET /v1/loan-originators/template` + +**Permission**: `READ_LOAN_ORIGINATOR` + +Returns a pre-generated `externalId`, available status values, and code value options for originator type and channel type. + +==== Update a Loan Originator + +`PUT /v1/loan-originators/{originatorId}` + +`PUT /v1/loan-originators/external-id/{externalId}` + +**Permission**: `UPDATE_LOAN_ORIGINATOR` + +[source,json] +---- +{ + "status": "PENDING" +} +---- + +Updatable fields: `name`, `status`, `originatorTypeId`, `channelTypeId`. Only changed fields need to be included. + +**Response:** + +[source,json] +---- +{ + "resourceId": 13, + "resourceExternalId": "best-merchant-us-east", + "changes": { + "status": "PENDING" + } +} +---- + +|=== +| HTTP Code | Description + +| 200 | Updated successfully +| 400 | Unsupported parameter (e.g. `externalId`) or incorrect format +| 404 | Originator not found +|=== + +[IMPORTANT] +==== +The `externalId` field **cannot be updated** after creation. +==== + +==== Delete a Loan Originator + +`DELETE /v1/loan-originators/{originatorId}` + +`DELETE /v1/loan-originators/external-id/{externalId}` + +**Permission**: `DELETE_LOAN_ORIGINATOR` + +|=== +| HTTP Code | Description + +| 200 | Deleted successfully +| 403 | Originator is currently mapped to one or more loans +| 404 | Originator not found +|=== + +[IMPORTANT] +==== +An originator cannot be deleted if it is currently mapped to any loans. +==== + +=== Loan-Originator Mapping APIs + +==== Attach Originator to Loan + +`POST /v1/loans/{loanId}/originators/{originatorId}` + +`POST /v1/loans/{loanId}/originators/external-id/{originatorExternalId}` + +`POST /v1/loans/external-id/{loanExternalId}/originators/{originatorId}` + +`POST /v1/loans/external-id/{loanExternalId}/originators/external-id/{originatorExternalId}` + +**Permission**: `ATTACH_LOAN_ORIGINATOR` + +No request body. + +**Response:** + +[source,json] +---- +{ + "loanId": 45, + "loanExternalId": "11793428-12cb-42fe-ab9f-72b4ddf2453a", + "originatorId": 13, + "originatorExternalId": "best-merchant-us-east" +} +---- + +|=== +| HTTP Code | Description + +| 200 | Attached successfully +| 403 | Loan is not in Submitted and Pending Approval status, originator is not ACTIVE, or mapping already exists +| 404 | Loan or originator not found +|=== + +==== Detach Originator from Loan + +`DELETE /v1/loans/{loanId}/originators/{originatorId}` + +`DELETE /v1/loans/{loanId}/originators/external-id/{originatorExternalId}` + +`DELETE /v1/loans/external-id/{loanExternalId}/originators/{originatorId}` + +`DELETE /v1/loans/external-id/{loanExternalId}/originators/external-id/{originatorExternalId}` + +**Permission**: `DETACH_LOAN_ORIGINATOR` + +No request body. Response format is the same as Attach. + +|=== +| HTTP Code | Description + +| 200 | Detached successfully +| 403 | Loan is not in Submitted and Pending Approval status +| 404 | Loan, originator, or mapping not found +|=== + +==== Retrieve Originators for a Loan + +`GET /v1/loans/{loanId}/originators` + +`GET /v1/loans/external-id/{loanExternalId}/originators` + +**Permission**: `READ_LOAN` + +[source,json] +---- +{ + "originators": [ + { + "id": 13, + "externalId": "best-merchant-us-east", + "name": "Best Merchant in US", + "status": "ACTIVE", + "originatorType": { + "id": 12, + "name": "MERCHANT", + "active": true, + "mandatory": false + }, + "channelType": { + "id": 44, + "name": "ONLINE", + "active": true, + "mandatory": false + } + } + ] +} +---- + +==== Originators via Retrieve Loan API + +Originator details can also be fetched as part of the standard Retrieve Loan API using the `associations` query parameter: + +`GET /v1/loans/{loanId}?associations=originators` + +`GET /v1/loans/external-id/{loanExternalId}?associations=originators` + +**Permission**: `READ_LOAN` + +The `originators` association is also included when `associations=all` is used. Since the `associations` parameter defaults to `all`, originator data is included in the Retrieve Loan response by default (even without an explicit `associations` parameter). The response adds an `originators` field to the loan object with the same structure as the dedicated endpoint above. + +==== Attach/Detach Validation Rules + +[IMPORTANT] +==== +* Attach and detach are **only allowed while the loan is in Submitted and Pending Approval status** +* The same originator **cannot be attached twice** to the same loan +* The same originator **cannot be detached twice** (returns 404 if mapping does not exist) +* Only originators with `ACTIVE` status can be **attached** (no status restriction for detach) +==== + +=== Inline Originator Creation During Loan Application + +Originators can be provided as part of the loan creation request (`POST /v1/loans`): + +[source,json] +---- +{ + "...": "...", + "originators": [ + { + "id": 1, + "externalId": "XYZ", + "name": "PP Merchant", + "originatorTypeId": 1, + "channelTypeId": 2 + } + ] +} +---- + +* `id` or `externalId` is mandatory for each entry +* If `id` is provided: attaches the existing originator (lookup by `id` takes priority over `externalId`) +* If only `externalId` is provided: +** Attaches the existing originator if found +** Creates a new originator and attaches it (only when `enable-originator-creation-during-loan-application` is `true`) +** Returns **403** if originator is not found and creation is not enabled +* `name`, `originatorTypeId`, `channelTypeId` are optional, used only when creating a new entry +* Newly created originators are automatically assigned `ACTIVE` status +* Duplicate originators within the same request are silently skipped + +== Business Events Integration + +Originator details are automatically included in all loan and loan transaction external business events. + +An `OriginatorDetailsV1` Avro record is added as an optional `originators` field (list) to: + +* `LoanAccountDataV1.avsc` -- all loan-centric events +* `LoanTransactionDataV1.avsc` -- all loan transaction-centric events + +[source,json] +---- +{ + "name": "OriginatorDetailsV1", + "fields": [ + {"name": "id", "type": ["null", "long"]}, + {"name": "externalId", "type": ["null", "string"]}, + {"name": "name", "type": ["null", "string"]}, + {"name": "status", "type": ["null", "string"]}, + {"name": "originatorType", "type": ["null", "CodeValueDataV1"]}, + {"name": "channelType", "type": ["null", "CodeValueDataV1"]} + ] +} +---- + +The field is **optional with default `null`**, preserving backward compatibility for existing event consumers. + +On any loan or loan transaction event publication, the enricher fetches originator mappings for the loan, builds the `originators` list from the registry, and attaches it to the event payload. + +== Security and Permissions + +[cols="1,1", options="header"] +|=== +| Permission | Description + +| `CREATE_LOAN_ORIGINATOR` +| Create originator records + +| `READ_LOAN_ORIGINATOR` +| View originator registry records and template data + +| `READ_LOAN` +| View loan-originator associations (`GET /v1/loans/.../originators`) + +| `UPDATE_LOAN_ORIGINATOR` +| Modify originator records + +| `DELETE_LOAN_ORIGINATOR` +| Delete originator records (only if not mapped to loans) + +| `ATTACH_LOAN_ORIGINATOR` +| Attach an originator to a loan + +| `DETACH_LOAN_ORIGINATOR` +| Detach an originator from a loan +|=== + +== Reporting + +Originator external IDs are included in the following stretchy reports: + +* **Transaction Summary Report** +* **Trial Balance Report** + +Report queries join `m_loan_originator_mapping` and `m_loan_originator` via a CTE. Multiple originators per loan are aggregated into a comma-separated string (`STRING_AGG` on PostgreSQL, `GROUP_CONCAT` on MySQL). The resulting column is `originator_external_ids`. Loans without originators have `NULL` in this column. diff --git a/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc b/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc index e9e876525c4..449219d91eb 100644 --- a/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc +++ b/fineract-doc/src/docs/en/chapters/testing/cucumber.adoc @@ -24,7 +24,7 @@ Apache Fineract's E2E test suite provides comprehensive coverage of business fun === Required Software * *Java 21*: Apache Fineract requires Java 21 (Azul Zulu JDK recommended) -* *Database*: MariaDB 11.5.2, PostgreSQL 17.4, or MySQL 9.1 +* *Database*: MariaDB 12.2, PostgreSQL 17.4, or MySQL 9.1 * *Git*: For source code management * *Gradle 8.14.3*: Included via wrapper @@ -149,12 +149,15 @@ mysql -u root -pmysql fineract_default -e \ Method 2 - Via API (After Fineract is Running): [source,bash] ---- -curl -X PUT https://localhost:8443/fineract-provider/api/v1/configurations/name/enable-business-date \ +curl -k -X PUT https://localhost:8443/fineract-provider/api/v1/configurations/name/enable-business-date \ -H "Authorization: Basic bWlmb3M6cGFzc3dvcmQ=" \ + -H "Fineract-Platform-TenantId: default" \ -H "Content-Type: application/json" \ -d '{"enabled": true}' ---- +NOTE: The `Fineract-Platform-TenantId` header is required. Without it, the request can fail with HTTP 400 because tenant context is missing. + *Verification*: [source,bash] ---- @@ -170,10 +173,12 @@ mysql -u root -pmysql fineract_default -e \ [source,bash] ---- -# Start Fineract in background -./gradlew bootRun +# Start Fineract with test profile enabled for E2E +./gradlew bootRun -Dspring.profiles.active=test ---- +IMPORTANT: When running E2E tests that hit endpoints/APIs backed by beans annotated with `@Profile(FineractProfiles.TEST)`, the provider startup must include `-Dspring.profiles.active=test`. Without this, test-profile-only components are not loaded. + Wait for Fineract to be fully started. You can verify by checking: [source,bash] ---- diff --git a/fineract-doc/src/docs/en/chapters/testing/integration.adoc b/fineract-doc/src/docs/en/chapters/testing/integration.adoc index 2df8236d42a..15c1ae6de2e 100644 --- a/fineract-doc/src/docs/en/chapters/testing/integration.adoc +++ b/fineract-doc/src/docs/en/chapters/testing/integration.adoc @@ -26,7 +26,7 @@ Integration tests in Apache Fineract validate the complete API layer and busines === Required Software * *Java 21*: Apache Fineract requires Java 21 (Azul Zulu JDK recommended) -* *Database*: MariaDB 11.5.2, PostgreSQL 17.4, or MySQL 9.1 +* *Database*: MariaDB 12.2, PostgreSQL 17.4, or MySQL 9.1 * *Git*: For source code management * *Gradle 8.14.3*: Included via wrapper * *12GB RAM*: Recommended for test execution diff --git a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java index b29b993462b..f72b6350d87 100644 --- a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java +++ b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/api/ImagesApiResource.java @@ -197,7 +197,8 @@ public ImageCreateResponse createImage(@PathParam(DOCUMENT_API_PARAM_ENTITY_TYPE @PUT @Consumes(MediaType.MULTIPART_FORM_DATA) - @RequestBody(description = "Update image", content = { @Content(mediaType = MediaType.MULTIPART_FORM_DATA) }) + @RequestBody(description = "Update image", content = { + @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @io.swagger.v3.oas.annotations.media.Schema(type = "object")) }) public ImageCreateResponse updateImage(@PathParam(DOCUMENT_API_PARAM_ENTITY_TYPE) final String entityName, @PathParam(DOCUMENT_API_PARAM_ENTITY_ID) final Long entityId, @HeaderParam(CONTENT_LENGTH) final Long fileSize, @FormDataParam(DOCUMENT_API_PARAM_FILE) final InputStream inputStream, diff --git a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java index 578201f1d15..c05c7d5ef52 100644 --- a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java +++ b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/exception/DocumentNotFoundException.java @@ -18,9 +18,9 @@ */ package org.apache.fineract.infrastructure.documentmanagement.exception; -import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; -public class DocumentNotFoundException extends AbstractPlatformDomainRuleException { +public class DocumentNotFoundException extends AbstractPlatformResourceNotFoundException { public DocumentNotFoundException(final Long id) { super("error.msg.document.id.invalid", "Document with identifier " + id + " does not exist", id); diff --git a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java index 056a1112260..7c64d18a9dc 100644 --- a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java +++ b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImpl.java @@ -28,7 +28,6 @@ import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter; import org.apache.fineract.infrastructure.documentmanagement.data.DocumentContent; import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; -import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentInvalidRequestException; import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException; import org.springframework.stereotype.Service; @@ -45,25 +44,15 @@ public class ImageReadPlatformServiceImpl implements ImageReadPlatformService { @Override public DocumentContent retrieveImage(final String entityType, final Long entityId) { - try { - return imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(entityType)).findFirst() - .flatMap(imageIdAdapter -> imageIdAdapter.get(entityId)) - .flatMap( - imageIdResult -> imageRepository.findById(imageIdResult.getId()) - .map(image -> DocumentContent - .builder().fileName( - FilenameUtils.getName(image.getLocation())) - .format(FilenameUtils.getExtension(image.getLocation())) - .displayName(imageIdResult.getDisplayName()) - .contentType( - contentDetectorManager - .detect(ContentDetectorContext.builder() - .fileName(FilenameUtils.getName(image.getLocation())).build()) - .getMimeType()) - .stream(storeService.download(image.getLocation())).build())) - .orElseThrow(() -> new DocumentNotFoundException(entityType, entityId, -1L)); - } catch (final Exception e) { - throw new DocumentInvalidRequestException(e); - } + return imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(entityType)).findFirst() + .flatMap(imageIdAdapter -> imageIdAdapter.get(entityId)) + .flatMap(imageIdResult -> imageRepository.findById(imageIdResult.getId()).map(image -> DocumentContent.builder() + .fileName(FilenameUtils.getName(image.getLocation())).format(FilenameUtils.getExtension(image.getLocation())) + .displayName(imageIdResult.getDisplayName()) + .contentType(contentDetectorManager + .detect(ContentDetectorContext.builder().fileName(FilenameUtils.getName(image.getLocation())).build()) + .getMimeType()) + .stream(storeService.download(image.getLocation())).build())) + .orElseThrow(() -> new DocumentNotFoundException(entityType, entityId, -1L)); } } diff --git a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java index 1538788bee5..6d25a80bda6 100644 --- a/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java +++ b/fineract-document/src/main/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImpl.java @@ -65,6 +65,7 @@ public ImageCreateResponse createImage(final ImageCreateRequest request) { // TODO: keeping the path segment always "clients" not consistent how this works with documents request.setEntityType(DEFAULT_ENTITY_TYPE); } + final var imageEntityType = normalizeImageEntityType(request.getEntityType()); if (StringUtils.isEmpty(request.getFileName())) { // NOTE: defacto limiting the uploads to JPEG files, same behavior as before @@ -83,12 +84,11 @@ public ImageCreateResponse createImage(final ImageCreateRequest request) { } // TODO: make "prefix" configurable? - var path = getPath(STORE_PREFIX, request.getEntityType(), request.getEntityId(), request.getFileName(), - storeService.getDelimiter()); + var path = getPath(STORE_PREFIX, imageEntityType, request.getEntityId(), request.getFileName(), storeService.getDelimiter()); final var imagePath = storeService.upload(path, request.getStream(), request.getType()); - final var result = imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(request.getEntityType())).findFirst() + final var result = imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(imageEntityType)).findFirst() .flatMap(imageIdAdapter -> imageIdAdapter.get(request.getEntityId())) .flatMap(imageIdResult -> imageRepository.findById(imageIdResult.getId())).map(image -> { // delete old image @@ -99,7 +99,7 @@ public ImageCreateResponse createImage(final ImageCreateRequest request) { .map(image -> image.setLocation(imagePath).setStorageType(storeService.getType().getValue())).map(imageRepository::save) .map(image -> ImageCreateResponse.builder().resourceId(image.getId()).build()); - imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(request.getEntityType())).findFirst() + imageIdAdapters.stream().filter(imageIdAdapter -> imageIdAdapter.accept(imageEntityType)).findFirst() .ifPresent(imageIdAdapter -> result.ifPresent( imageCreateResponse -> imageIdAdapter.set(request.getEntityId(), imageCreateResponse.getResourceId()))); @@ -135,6 +135,16 @@ private Optional delete(final String entityType, final Long }); } + private String normalizeImageEntityType(final String entityType) { + if ("staff".equalsIgnoreCase(entityType)) { + return "staff"; + } + if ("clients".equalsIgnoreCase(entityType)) { + return DEFAULT_ENTITY_TYPE; + } + return entityType; + } + private String getPath(final String prefix, final String entityType, final Long entityId, final String fileName, String delimiter) { requireNonNull(prefix); requireNonNull(entityType); diff --git a/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImplTest.java b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImplTest.java new file mode 100644 index 00000000000..dc214f6b497 --- /dev/null +++ b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageReadPlatformServiceImplTest.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.documentmanagement.service; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.apache.fineract.infrastructure.contentstore.detector.ContentDetectorManager; +import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService; +import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter; +import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; +import org.apache.fineract.infrastructure.documentmanagement.exception.DocumentNotFoundException; +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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ImageReadPlatformServiceImplTest { + + @Mock + private ImageRepository imageRepository; + @Mock + private ContentStoreService storeService; + @Mock + private ContentDetectorManager contentDetectorManager; + @Mock + private EntityImageIdAdapter imageIdAdapter; + + private ImageReadPlatformServiceImpl imageReadPlatformService; + + @BeforeEach + void setUp() { + List imageIdAdapters = Collections.singletonList(imageIdAdapter); + imageReadPlatformService = new ImageReadPlatformServiceImpl(imageIdAdapters, storeService, imageRepository, contentDetectorManager); + } + + @Test + void testRetrieveImage_NotFound_ThrowsDocumentNotFoundException() { + // Arrange + String entityType = "clients"; + Long entityId = 1L; + + when(imageIdAdapter.accept(anyString())).thenReturn(true); + when(imageIdAdapter.get(entityId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(DocumentNotFoundException.class, () -> { + imageReadPlatformService.retrieveImage(entityType, entityId); + }); + } +} diff --git a/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java new file mode 100644 index 00000000000..a3c71c2c8a9 --- /dev/null +++ b/fineract-document/src/test/java/org/apache/fineract/infrastructure/documentmanagement/service/ImageWritePlatformServiceImplTest.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.documentmanagement.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import org.apache.fineract.infrastructure.contentstore.data.ContentStoreType; +import org.apache.fineract.infrastructure.contentstore.detector.ContentDetectorManager; +import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService; +import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter; +import org.apache.fineract.infrastructure.documentmanagement.data.ImageCreateRequest; +import org.apache.fineract.infrastructure.documentmanagement.domain.Image; +import org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ImageWritePlatformServiceImplTest { + + @Mock + private EntityImageIdAdapter clientImageIdAdapter; + @Mock + private EntityImageIdAdapter staffImageIdAdapter; + @Mock + private ContentStoreService contentStoreService; + @Mock + private ImageRepository imageRepository; + @Mock + private ContentDetectorManager contentDetectorManager; + + private ImageWritePlatformServiceImpl underTest; + + @BeforeEach + void setUp() { + underTest = new ImageWritePlatformServiceImpl(List.of(clientImageIdAdapter, staffImageIdAdapter), contentStoreService, + imageRepository, contentDetectorManager); + + when(contentStoreService.getDelimiter()).thenReturn("/"); + when(contentStoreService.getType()).thenReturn(ContentStoreType.FILE_SYSTEM); + when(contentStoreService.upload(anyString(), any(InputStream.class), anyString())) + .thenAnswer(invocation -> invocation.getArgument(0)); + when(imageRepository.save(any(Image.class))).thenAnswer(invocation -> { + Image image = invocation.getArgument(0); + image.setId(99L); + return image; + }); + } + + @Test + void createImageShouldStoreStaffImageUnderStaffDirectory() { + Long entityId = 7L; + when(staffImageIdAdapter.accept("staff")).thenReturn(true); + when(staffImageIdAdapter.get(entityId)).thenReturn(Optional.empty()); + when(staffImageIdAdapter.set(eq(entityId), anyLong())).thenReturn(Optional.empty()); + when(clientImageIdAdapter.accept(anyString())).thenReturn(false); + + ImageCreateRequest request = ImageCreateRequest.builder().entityType("STAFF").entityId(entityId).fileName("profile.png") + .type("image/png").stream(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).build(); + + var response = underTest.createImage(request); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + verify(contentStoreService).upload(pathCaptor.capture(), any(InputStream.class), eq("image/png")); + assertEquals("images/staff/7/profile.png", pathCaptor.getValue()); + assertEquals(99L, response.getResourceId()); + } + + @Test + void createImageShouldStoreClientImageUnderClientsDirectory() { + Long entityId = 7L; + when(clientImageIdAdapter.accept("clients")).thenReturn(true); + when(clientImageIdAdapter.get(entityId)).thenReturn(Optional.empty()); + when(clientImageIdAdapter.set(eq(entityId), anyLong())).thenReturn(Optional.empty()); + + ImageCreateRequest request = ImageCreateRequest.builder().entityType("CLIENTS").entityId(entityId).fileName("profile.png") + .type("image/png").stream(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).build(); + + var response = underTest.createImage(request); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + verify(contentStoreService).upload(pathCaptor.capture(), any(InputStream.class), eq("image/png")); + assertEquals("images/clients/7/profile.png", pathCaptor.getValue()); + assertEquals(99L, response.getResourceId()); + } +} diff --git a/fineract-e2e-tests-core/build.gradle b/fineract-e2e-tests-core/build.gradle index d4cb2af8f43..53c3ff7e6ff 100644 --- a/fineract-e2e-tests-core/build.gradle +++ b/fineract-e2e-tests-core/build.gradle @@ -99,6 +99,10 @@ dependencies { testImplementation 'io.github.classgraph:classgraph:4.8.179' testImplementation 'org.apache.commons:commons-collections4:4.4' + + testImplementation 'org.springframework:spring-jdbc' + testImplementation 'org.postgresql:postgresql' + testImplementation 'org.mariadb.jdbc:mariadb-java-client' } tasks.withType(JavaCompile).configureEach { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/TestDatabaseConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/TestDatabaseConfiguration.java new file mode 100644 index 00000000000..909501b7f76 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/config/TestDatabaseConfiguration.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.config; + +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +@Configuration +@Slf4j +public class TestDatabaseConfiguration { + + @Value("${fineract-test.db.protocol}") + private String protocol; + + @Value("${fineract-test.db.hostname}") + private String hostname; + + @Value("${fineract-test.db.port}") + private String port; + + @Value("${fineract-test.db.name}") + private String dbName; + + @Value("${fineract-test.db.username}") + private String username; + + @Value("${fineract-test.db.password}") + private String password; + + @Bean + public DataSource testDataSource() { + // DriverManagerDataSource creates a new connection per call (no pooling). + // This is intentional for lightweight e2e test usage — no pool management overhead needed. + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + String url = protocol + "://" + hostname + ":" + port + "/" + dbName; + log.debug("Test database URL: {}", url); + dataSource.setUrl(url); + dataSource.setUsername(username); + dataSource.setPassword(password); + return dataSource; + } + + @Bean + public JdbcTemplate testJdbcTemplate(DataSource testDataSource) { + return new JdbcTemplate(testDataSource); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductType.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductType.java index b84f47ccc18..11e570e9f7e 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductType.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/ChargeProductType.java @@ -36,7 +36,8 @@ public enum ChargeProductType { CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_PERCENT(14L), // LOAN_INSTALLMENT_FEE_FLAT(15L), // LOAN_INSTALLMENT_FEE_PERCENTAGE_AMOUNT(16L), // - LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST(17L); // + LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST(17L), // + LOAN_DISBURSEMENT_PERCENTAGE_AMOUNT_PLUS_INTEREST_FEE(18L); // public final Long value; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java index 5a02805d7f0..c3d2e8c9308 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/LoanStatus.java @@ -27,6 +27,8 @@ public enum LoanStatus { SUBMITTED_AND_PENDING_APPROVAL(100), // APPROVED(200), // ACTIVE(300), // + TRANSFER_IN_PROGRESS(303), // + TRANSFER_ON_HOLD(304), // WITHDRAWN(400), // REJECTED(500), // CLOSED_OBLIGATIONS_MET(600), // diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/codevalue/CodeValueResolver.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/codevalue/CodeValueResolver.java index 0c8409d5b57..fdb90f93a05 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/codevalue/CodeValueResolver.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/codevalue/CodeValueResolver.java @@ -53,7 +53,7 @@ public long resolve(Long codeId, CodeValue codeValue) { public long resolve(String codeName, String codeValue) { log.debug("Resolving code value by code id and name [{}]", codeValue); List codeValuesResponses = ok( - () -> fineractClient.codeValues().retrieveAllCodeValues1(codeName, Map.of())); + () -> fineractClient.codeValues().retrieveAllCodeValuesByCodeName(codeName, Map.of())); GetCodeValuesDataResponse foundPtr = codeValuesResponses.stream().filter(ptr -> codeValue.equals(ptr.getName())).findAny() .orElseThrow(() -> new IllegalArgumentException("Code Value [%s] not found for Code [%s]".formatted(codeValue, codeName))); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java index f9f97f312c4..347f3fca3a0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/job/DefaultJob.java @@ -28,7 +28,8 @@ public enum DefaultJob implements Job { ACCRUAL_ACTIVITY_POSTING("Accrual Activity Posting", "ACC_ACPO"), // ADD_ACCRUAL_TRANSACTIONS_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS( "Add Accrual Transactions For Loans With Income Posted As Transactions", "LA_AATR"), // - RECALCULATE_INTEREST_FOR_LOANS("Recalculate Interest For Loans", "LA_RINT"); // + RECALCULATE_INTEREST_FOR_LOANS("Recalculate Interest For Loans", "LA_RINT"), // + WORKING_CAPITAL_LOAN_COB("Working Capital Loan COB", "WC_COB"); // private final String customName; private final String shortName; diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index 031e8cc5c46..6bbe66d4fba 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -190,6 +190,8 @@ public enum DefaultLoanProduct implements LoanProduct { LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_CHARGEBACK, // LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ZERO_INT_CHARGE_OFF, // LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ACCELERATE_MATURITY, // + LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME, // + LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC, // ; @Override diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java new file mode 100644 index 00000000000..bb35365fc0b --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/DefaultWorkingCapitalLoanProduct.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.data.workingcapitalproduct; + +public enum DefaultWorkingCapitalLoanProduct implements WorkingCapitalLoanProduct { + + WCLP, // + WCLP_FOR_UPDATE; // + + @Override + public String getName() { + return name(); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/WorkingCapitalLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/WorkingCapitalLoanProduct.java new file mode 100644 index 00000000000..048139d0edf --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/workingcapitalproduct/WorkingCapitalLoanProduct.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.data.workingcapitalproduct; + +public interface WorkingCapitalLoanProduct { + + String getName(); +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java index 9759148adec..ba11f3a75b0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java @@ -1770,6 +1770,11 @@ public PostLoanProductsRequest defaultLoanProductsRequestLP2BuyDownFees() { .incomeFromBuyDownAccountId(accountTypeResolver.resolve(DefaultAccountType.INCOME_FROM_BUY_DOWN));// } + public PostLoanProductsRequest defaultLoanProductsRequestLP2BuyDownFeesFeeIncome() { + return defaultLoanProductsRequestLP2BuyDownFees()// + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE);// + } + public PostLoanProductsRequest defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithBuyDownFee() { Long chargeOffReasonId = codeHelper.retrieveCodeByName(CHARGE_OFF_REASONS).getId(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java index 77c3089fe1b..f3c2d4385e8 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanRequestFactory.java @@ -120,7 +120,7 @@ public PostLoansRequest defaultLoansRequest(Long clientId) { .transactionProcessingStrategyCode(DEFAULT_TRANSACTION_PROCESSING_STRATEGY_CODE)// .dateFormat(DATE_FORMAT)// .graceOnArrearsAgeing(3)// - .maxOutstandingLoanBalance(new BigDecimal(10000)); + ; } public PostLoansRequest defaultProgressiveLoansRequest(final Long clientId) { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java new file mode 100644 index 00000000000..22817033afd --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/WorkingCapitalRequestFactory.java @@ -0,0 +1,159 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.factory; + +import static org.apache.fineract.test.data.DaysInYearType.DAYS365; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.CURRENCY_CODE; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.CURRENCY_CODE_USD; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.DATE_FORMAT; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.DAYS_IN_MONTH_TYPE_30; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.DAYS_IN_YEAR_TYPE_360; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.DELINQUENCY_BUCKET_ID; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.FUND_ID; +import static org.apache.fineract.test.factory.LoanProductsRequestFactory.LOCALE_EN; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.models.PaymentAllocationOrder; +import org.apache.fineract.client.models.PostAllowAttributeOverrides; +import org.apache.fineract.client.models.PostPaymentAllocation; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.test.helper.Utils; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class WorkingCapitalRequestFactory { + + private final LoanProductsRequestFactory loanProductsRequestFactory; + + public static final String WCLP_NAME_PREFIX = "WCLP-"; + public static final String WCLP_DESCRIPTION = "Working Capital Loan Product"; + public static final String PENALTY = "PENALTY"; + public static final String FEE = "FEE"; + public static final String PRINCIPAL = "PRINCIPAL"; + + public PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductRequest() { + String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10); + String shortName = loanProductsRequestFactory.generateShortNameSafely(); + + return new PostWorkingCapitalLoanProductsRequest()// + .name(name)// + .shortName(shortName)// + .description(WCLP_DESCRIPTION)// + .fundId(FUND_ID)// + .periodPaymentRate(new BigDecimal(1))// + .repaymentFrequencyType(PostWorkingCapitalLoanProductsRequest.RepaymentFrequencyTypeEnum.DAYS)// + .repaymentEvery(DAYS_IN_MONTH_TYPE_30)// + .startDate(null)// + .closeDate(null)// + .currencyCode(CURRENCY_CODE)// + .digitsAfterDecimal(2)// + .inMultiplesOf(1)// + .principal(new BigDecimal(100))// + .minPrincipal(new BigDecimal(10))// + .maxPrincipal(new BigDecimal(100000))// + .amortizationType(PostWorkingCapitalLoanProductsRequest.AmortizationTypeEnum.EIR)// + .npvDayCount(DAYS_IN_YEAR_TYPE_360)// + .delinquencyBucketId(DELINQUENCY_BUCKET_ID.longValue())// + .dateFormat(DATE_FORMAT)// + .locale(LOCALE_EN)// + .paymentAllocation(List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), + List.of(PENALTY, FEE, PRINCIPAL))));// + } + + public PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductRequestUpdate() { + String name = Utils.randomStringGenerator(WCLP_NAME_PREFIX, 10); + String shortName = loanProductsRequestFactory.generateShortNameSafely(); + + PostAllowAttributeOverrides allowAttributeOverrides = new PostAllowAttributeOverrides().delinquencyBucketClassification(true) + .discountDefault(false).flatPercentageAmount(true).periodPaymentFrequencyType(false).periodPaymentFrequency(true); + + return new PutWorkingCapitalLoanProductsProductIdRequest()// + .name(name)// + .shortName(shortName)// + .description(WCLP_DESCRIPTION)// + .fundId(FUND_ID)// + .periodPaymentRate(new BigDecimal(1))// + .repaymentFrequencyType(PutWorkingCapitalLoanProductsProductIdRequest.RepaymentFrequencyTypeEnum.MONTHS)// + .repaymentEvery(1)// + .startDate(null)// + .closeDate(null)// + .currencyCode(CURRENCY_CODE_USD)// + .digitsAfterDecimal(2)// + .inMultiplesOf(1)// + .principal(new BigDecimal(200))// + .minPrincipal(new BigDecimal(15))// + .maxPrincipal(new BigDecimal(300000))// + .amortizationType(PutWorkingCapitalLoanProductsProductIdRequest.AmortizationTypeEnum.EIR)// + .npvDayCount(DAYS365.value)// + .delinquencyBucketId(null)// + .dateFormat(DATE_FORMAT)// + .locale(LOCALE_EN)// + .allowAttributeOverrides(allowAttributeOverrides).paymentAllocation(List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), // + List.of(FEE, PRINCIPAL, PENALTY))));// + } + + public List invalidNumberOfPaymentAllocationRulesForWorkingCapitalLoanProductCreateRequest() { + return List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), // + List.of(FEE, PRINCIPAL, PENALTY, "INTEREST")));// + } + + public List invalidPaymentAllocationRulesForWorkingCapitalLoanProductCreateRequest() { + return List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), // + List.of(FEE, PRINCIPAL, "INTEREST")));// + } + + public List invalidNumberOfPaymentAllocationRulesForWorkingCapitalLoanProductUpdateRequest() { + return List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), // + List.of(FEE, PRINCIPAL, PENALTY, "INTEREST")));// + } + + public List invalidPaymentAllocationRulesForWorkingCapitalLoanProductUpdateRequest() { + return List.of(// + createPaymentAllocation(PostPaymentAllocation.TransactionTypeEnum.DEFAULT.getValue(), // + List.of(FEE, PRINCIPAL, "INTEREST")));// + } + + public static PostPaymentAllocation createPaymentAllocation(String transactionType, List paymentAllocationRules) { + PostPaymentAllocation.TransactionTypeEnum transactionTypeName = PostPaymentAllocation.TransactionTypeEnum.valueOf(transactionType); + PostPaymentAllocation paymentAllocationData = new PostPaymentAllocation(); + paymentAllocationData.setTransactionType(transactionTypeName); + + List paymentAllocationOrders = new ArrayList<>(); + for (int i = 0; i < paymentAllocationRules.size(); i++) { + PaymentAllocationOrder e = new PaymentAllocationOrder(); + e.setOrder(i + 1); + e.setPaymentAllocationRule(paymentAllocationRules.get(i)); + paymentAllocationOrders.add(e); + } + + paymentAllocationData.setPaymentAllocationOrder(paymentAllocationOrders); + return paymentAllocationData; + } + +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index cc84aa03fc7..387d57bcff3 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -77,6 +77,19 @@ public static String setCurrencyNullValueMandatoryFailure() { return "The parameter 'currencies' is mandatory."; } + public static String currencyNotFound(String currencyCode) { + return String.format("Currency with code '%s' not found in currency options", currencyCode); + } + + public static String wrongCurrencyField(String currencyCode, String fieldName, Object actual, Object expected) { + return String.format("Wrong %s for currency '%s'. Actual value is: %s - But expected value is: %s", fieldName, currencyCode, actual, + expected); + } + + public static String wrongSelectedCurrencies(List actual, List expected) { + return String.format("Wrong selected currencies. Actual value is: %s - But expected value is: %s", actual, expected); + } + public static String disburseDateFailure(Integer loanId) { String loanIdStr = parseLoanIdToString(loanId); return String.format("The date on which a loan with identifier : %s is disbursed cannot be in the future.", loanIdStr); @@ -115,6 +128,10 @@ public static String disburseIsNotAllowedFailure() { return "Loan Disbursal is not allowed. Loan Account is not in approved and not disbursed state."; } + public static String disburseIsNotAllowedExceedApprovedAmountFailure() { + return "Loan can't be disbursed, disburse amount is exceeding approved principal."; + } + public static String loanSubmitDateInFutureFailureMsg() { return "The date on which a loan is submitted cannot be in the future."; } @@ -998,4 +1015,33 @@ public static String reAmortizeClosedLoanFailure() { public static String reAmortizeSameDateFailure() { return "Validation errors: [id] Loan reamortization can only be done once a day. There has already been a reamortization done for today"; } + + public static String incorrectExpectedValueInResponse() { + return "The parameter is not matching to expected."; + } + + public static String fieldValueNullOrEmptyMandatoryFailure(String fieldName) { + return String.format("The parameter `%s` is mandatory.", fieldName); + } + + public static String fieldValueMoreMaxLengthAllowedFailure(String fieldName, int maxAllowedLength) { + return String.format("The parameter `%s` exceeds max length of %d.", fieldName, maxAllowedLength); + } + + public static String fieldValueZeroValueFailure(String fieldName) { + return String.format("The parameter `%s` must be greater than 0.", fieldName); + } + + public static String paymentAllocationRulesInvalidNumberFailure(int actualNumberOfPaymentAllocationRules) { + return String.format("Each provided payment allocation must contain exactly 3 allocation rules, but %d were provided", + actualNumberOfPaymentAllocationRules); + } + + public static String paymentAllocationRulesInvalidValueFailure() { + return "One or more payment allocation types are invalid or not recognized"; + } + + public static String workingCapitalLoanProductIdentifiedDoesNotExistFailure(String identifierId) { + return String.format("Working Capital Loan Product with identifier %s does not exist", identifierId); + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/GlobalConfigurationHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/GlobalConfigurationHelper.java index 7a2d6a64641..2ca73cfc3c5 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/GlobalConfigurationHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/GlobalConfigurationHelper.java @@ -50,7 +50,7 @@ private void switchAndSetGlobalConfiguration(String configKey, boolean enabled, PutGlobalConfigurationsRequest updateRequest = new PutGlobalConfigurationsRequest().enabled(enabled).value(value); - ok(() -> fineractClient.globalConfiguration().updateConfiguration1(configId, updateRequest, Map.of())); + ok(() -> fineractClient.globalConfiguration().updateGlobalConfiguration(configId, updateRequest, Map.of())); GlobalConfigurationPropertyData updatedConfiguration = ok( () -> fineractClient.globalConfiguration().retrieveOneByName(configKey, Map.of())); boolean isEnabled = BooleanUtils.toBoolean(updatedConfiguration.getEnabled()); @@ -64,7 +64,7 @@ public void setGlobalConfigValueString(String configKey, String value) { PutGlobalConfigurationsRequest updateRequest = new PutGlobalConfigurationsRequest().enabled(true).stringValue(value); - ok(() -> fineractClient.globalConfiguration().updateConfiguration1(configId, updateRequest, Map.of())); + ok(() -> fineractClient.globalConfiguration().updateGlobalConfiguration(configId, updateRequest, Map.of())); GlobalConfigurationPropertyData updatedConfiguration = ok( () -> fineractClient.globalConfiguration().retrieveOneByName(configKey, Map.of())); boolean isEnabled = BooleanUtils.toBoolean(updatedConfiguration.getEnabled()); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java index 8427be5b021..5da2b2e0732 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/Utils.java @@ -59,6 +59,10 @@ public static String randomStringGenerator(final String prefix, final int len) { return randomStringGenerator(prefix, len, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); } + public static String randomStringGenerator(final int len) { + return randomStringGenerator("", len); + } + public static String randomFirstNameGenerator() { return firstNames.get(random.nextInt(firstNames.size())); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java index c1c5621a864..94c27c39fc2 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java @@ -30,7 +30,9 @@ import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.models.BusinessStep; import org.apache.fineract.client.models.BusinessStepRequest; +import org.apache.fineract.client.models.ConfiguredJobNamesDTO; import org.apache.fineract.client.models.JobBusinessStepConfigData; +import org.apache.fineract.client.models.JobBusinessStepDetail; import org.springframework.stereotype.Component; @RequiredArgsConstructor @@ -62,6 +64,46 @@ public void setWorkflowJobs() { logChanges(); } + /** + * Returns all job names that have business step configuration registered. + */ + public ConfiguredJobNamesDTO getConfiguredBusinessJobs() { + return ok(() -> fineractClient.businessStepConfiguration().retrieveAllConfiguredBusinessJobs(Map.of())); + } + + /** + * Returns the currently configured business steps for the given job. + * + * @param jobName + * the job name, e.g. {@code LOAN_CLOSE_OF_BUSINESS} + */ + public JobBusinessStepConfigData getConfiguredWorkflowSteps(String jobName) { + return ok(() -> fineractClient.businessStepConfiguration().retrieveAllConfiguredBusinessStep(jobName, Map.of())); + } + + /** + * Returns all available (registered) business steps for the given job. + * + * @param jobName + * the job name, e.g. {@code LOAN_CLOSE_OF_BUSINESS} + */ + public JobBusinessStepDetail getAvailableWorkflowSteps(String jobName) { + return ok(() -> fineractClient.businessStepConfiguration().retrieveAllAvailableBusinessStep(jobName, Map.of())); + } + + /** + * Replaces the configured business steps for the given job. + * + * @param jobName + * the job name, e.g. {@code LOAN_CLOSE_OF_BUSINESS} + * @param steps + * the ordered list of business steps to configure + */ + public void updateWorkflowSteps(String jobName, List steps) { + BusinessStepRequest request = new BusinessStepRequest().businessSteps(steps); + executeVoid(() -> fineractClient.businessStepConfiguration().updateJobBusinessStepConfig(jobName, request, Map.of())); + } + private void logChanges() { JobBusinessStepConfigData changesResponse = ok(() -> fineractClient.businessStepConfiguration() .retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, Map.of())); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkingCapitalLoanTestHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkingCapitalLoanTestHelper.java new file mode 100644 index 00000000000..f210d01c562 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkingCapitalLoanTestHelper.java @@ -0,0 +1,98 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.helper; + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Objects; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.test.data.LoanStatus; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class WorkingCapitalLoanTestHelper { + + private static final String TABLE_WC_LOAN = "m_wc_loan"; + private static final String TABLE_WC_LOAN_ACCOUNT_LOCKS = "m_wc_loan_account_locks"; + private static final int INITIAL_VERSION = 0; + private static final long ADMIN_USER_ID = 1L; + + private final JdbcTemplate testJdbcTemplate; + private final SimpleJdbcInsert wcLoanInsert; + + public WorkingCapitalLoanTestHelper(JdbcTemplate testJdbcTemplate) { + this.testJdbcTemplate = testJdbcTemplate; + this.wcLoanInsert = new SimpleJdbcInsert(testJdbcTemplate)// + .withTableName(TABLE_WC_LOAN)// + .usingGeneratedKeyColumns("id"); + } + + public Long insertActiveLoan() { + return insertLoan(LoanStatus.ACTIVE.getValue(), null); + } + + public String generateUniqueExternalId() { + return "EXT-" + UUID.randomUUID().toString().substring(0, 8); + } + + public String generateAccountNumber() { + return String.valueOf(System.currentTimeMillis()); + } + + public Long insertLoan(int loanStatusId, LocalDate lastClosedBusinessDate) { + Timestamp now = Timestamp.from(OffsetDateTime.now(ZoneOffset.UTC).toInstant()); + MapSqlParameterSource params = new MapSqlParameterSource()// + .addValue("account_no", generateAccountNumber()).addValue("external_id", generateUniqueExternalId()) + .addValue("version", INITIAL_VERSION)// + .addValue("created_by", ADMIN_USER_ID)// + .addValue("last_modified_by", ADMIN_USER_ID)// + .addValue("created_on_utc", now)// + .addValue("last_modified_on_utc", now)// + .addValue("loan_status_id", loanStatusId)// + .addValue("last_closed_business_date", lastClosedBusinessDate); + Number key = wcLoanInsert.executeAndReturnKey(params); + return Objects.requireNonNull(key, "Generated key must not be null").longValue(); + } + + public LocalDate getLastClosedBusinessDate(Long loanId) { + return testJdbcTemplate.queryForObject("SELECT last_closed_business_date FROM " + TABLE_WC_LOAN + " WHERE id = ?", LocalDate.class, + loanId); + } + + public int getVersion(Long loanId) { + return testJdbcTemplate.queryForObject("SELECT version FROM " + TABLE_WC_LOAN + " WHERE id = ?", Integer.class, loanId); + } + + public int countLocksByLoanId(Long loanId) { + return testJdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + TABLE_WC_LOAN_ACCOUNT_LOCKS + " WHERE loan_id = ?", Integer.class, + loanId); + } + + public void deleteById(Long loanId) { + testJdbcTemplate.update("DELETE FROM " + TABLE_WC_LOAN_ACCOUNT_LOCKS + " WHERE loan_id = ?", loanId); + testJdbcTemplate.update("DELETE FROM " + TABLE_WC_LOAN + " WHERE id = ?", loanId); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java index 269d3c9de07..e97fd26afc5 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/base/BaseFineractInitializerConfiguration.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.fineract.test.config.CacheConfiguration; +import org.apache.fineract.test.config.TestDatabaseConfiguration; import org.apache.fineract.test.initializer.global.FineractGlobalInitializerStep; import org.apache.fineract.test.initializer.scenario.FineractScenarioInitializerStep; import org.apache.fineract.test.initializer.suite.FineractSuiteInitializerStep; @@ -32,7 +33,7 @@ @Configuration @ComponentScan({ "org.apache.fineract.test.api", "org.apache.fineract.test.helper" }) @PropertySource("classpath:fineract-test-application.properties") -@Import({ CacheConfiguration.class }) +@Import({ CacheConfiguration.class, TestDatabaseConfiguration.class }) public class BaseFineractInitializerConfiguration { @Bean diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java index a3c6762a2ae..ede9c2cdbbf 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java @@ -113,7 +113,7 @@ private void waitForTransactionCommit() { public void clientEventCheck(PostClientsResponse clientCreationResponse) { waitForTransactionCommit(); - GetClientsClientIdResponse body = ok(() -> fineractClient.clients().retrieveOne11(clientCreationResponse.getClientId(), + GetClientsClientIdResponse body = ok(() -> fineractClient.clients().retrieveOneClient(clientCreationResponse.getClientId(), Map.of("staffInSelectedOfficeOnly", false))); Long clientId = Long.valueOf(body.getId()); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java index e18f6600988..33a68d3eb87 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java @@ -71,7 +71,7 @@ private void waitUntilJobIsFinished(Job job) { .until(() -> { log.debug("Waiting for job {} to finish", jobName); Long jobId = jobResolver.resolve(job); - GetJobsResponse getJobsResponse = ok(() -> fineractClient.schedulerJob().retrieveOne5(jobId)); + GetJobsResponse getJobsResponse = ok(() -> fineractClient.schedulerJob().retrieveOneSchedulerJob(jobId)); Boolean currentlyRunning = getJobsResponse.getCurrentlyRunning(); return BooleanUtils.isFalse(currentlyRunning); }); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java index e64c3496664..39ae500f533 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/assetexternalization/AssetExternalizationStepDef.java @@ -804,7 +804,7 @@ public void adminTransactionCommandTheWithType(String command, String type) thro String transferExternalId = testContext() .get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type); - externalAssetOwnersApi().transferRequestWithId1(transferExternalId, Map.of("command", command)); + externalAssetOwnersApi().transferRequestWithIdByExternalId(transferExternalId, Map.of("command", command)); } @When("Admin send {string} command to the transaction type {string} will throw error") @@ -813,7 +813,7 @@ public void adminTransactionCommandTheWithTypeThrowError(String command, String .get(TestContextKey.ASSET_EXTERNALIZATION_TRANSFER_EXTERNAL_ID_USER_GENERATED + "_" + type); CallFailedRuntimeException exception = fail( - () -> externalAssetOwnersApi().transferRequestWithId1(transferExternalId, Map.of("command", command))); + () -> externalAssetOwnersApi().transferRequestWithIdByExternalId(transferExternalId, Map.of("command", command))); assertThat(exception.getStatus()).as("Expected status code: 403").isEqualTo(403); } @@ -875,7 +875,7 @@ public void adminSendCommandAndItWillThrowError(String command, String transacti } CallFailedRuntimeException exception = fail( - () -> externalAssetOwnersApi().transferRequestWithId1(transferExternalId, Map.of("command", command))); + () -> externalAssetOwnersApi().transferRequestWithIdByExternalId(transferExternalId, Map.of("command", command))); assertThat(exception.getStatus()).as("Expected status code: 403").isEqualTo(403); } @@ -889,7 +889,7 @@ public void adminSendCommand(String command, String transactionType) throws IOEx transferExternalId = testContext().get(TestContextKey.ASSET_EXTERNALIZATION_SALES_TRANSFER_EXTERNAL_ID_FROM_RESPONSE); } - externalAssetOwnersApi().transferRequestWithId1(transferExternalId, Map.of("command", command)); + externalAssetOwnersApi().transferRequestWithIdByExternalId(transferExternalId, Map.of("command", command)); } @When("Admin set external asset owner loan product attribute {string} value {string} for loan product {string}") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java index b389dd1909c..c29839cf167 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java @@ -552,7 +552,7 @@ public void runBatchApiCreateAndApproveLoanRescheduleWithGivenUserLockedByCobErr // Create new user which cannot bypass loan COB execution PostUsersResponse createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE); Long createdUserId = createUserResponse.getResourceId(); - GetUsersUserIdResponse user = fineractFeignClient.users().retrieveOne32(createdUserId); + GetUsersUserIdResponse user = fineractFeignClient.users().retrieveOneUser(createdUserId); String authorizationString = user.getUsername() + ":" + PWD_USER_WITH_ROLE; Base64 base64 = new Base64(); headerMap.put("Authorization", @@ -849,7 +849,7 @@ public void givenClientCreated(int nr) throws IOException { Map clientQueryParams = new HashMap<>(); clientQueryParams.put("staffInSelectedOfficeOnly", false); - GetClientsClientIdResponse response = clientApi().retrieveOne12(clientExternalId, clientQueryParams); + GetClientsClientIdResponse response = clientApi().retrieveOneClientByExternalId(clientExternalId, clientQueryParams); assertThat(response.getId()).as(ErrorMessageHelper.idNull()).isNotNull(); } @@ -866,7 +866,7 @@ public void givenLoanCreated(int nr) throws IOException { Map loanQueryParams = new HashMap<>(); loanQueryParams.put("staffInSelectedOfficeOnly", false); - GetLoansLoanIdResponse response = loansApi().retrieveLoan1(loanExternalId, loanQueryParams); + GetLoansLoanIdResponse response = loansApi().retrieveLoanByExternalId(loanExternalId, loanQueryParams); assertThat(response.getId()).as(ErrorMessageHelper.idNull()).isNotNull(); } @@ -883,7 +883,7 @@ public void givenLoanApproved(int nr) throws IOException { Map loanQueryParams = new HashMap<>(); loanQueryParams.put("staffInSelectedOfficeOnly", false); - GetLoansLoanIdResponse response = loansApi().retrieveLoan1(loanExternalId, loanQueryParams); + GetLoansLoanIdResponse response = loansApi().retrieveLoanByExternalId(loanExternalId, loanQueryParams); GetLoansLoanIdStatus status = response.getStatus(); Integer statusIdActual = status.getId(); Integer statusIdExpected = LoanStatus.APPROVED.value; @@ -909,7 +909,7 @@ public void clientNotCreated(int nr) throws IOException { try { Map clientQueryParams = new HashMap<>(); clientQueryParams.put("staffInSelectedOfficeOnly", false); - clientApi().retrieveOne12(clientExternalId, clientQueryParams); + clientApi().retrieveOneClientByExternalId(clientExternalId, clientQueryParams); throw new IllegalStateException("Expected Feign exception but call succeeded"); } catch (org.apache.fineract.client.feign.FeignException e) { errorResponse = fromJson(e.responseBodyAsString(), ErrorResponse.class); @@ -948,7 +948,7 @@ public void loanNotCreated(int nr) throws IOException { // Feign throws exceptions on errors instead of returning error in response body ErrorResponse errorResponse = null; try { - loansApi().retrieveLoan1(loanExternalId, loanQueryParams); + loansApi().retrieveLoanByExternalId(loanExternalId, loanQueryParams); throw new IllegalStateException("Expected Feign exception but call succeeded"); } catch (org.apache.fineract.client.feign.FeignException e) { errorResponse = fromJson(e.responseBodyAsString(), ErrorResponse.class); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepConfigurationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepConfigurationStepDef.java new file mode 100644 index 00000000000..365a55409b1 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BusinessStepConfigurationStepDef.java @@ -0,0 +1,131 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.BusinessStep; +import org.apache.fineract.client.models.BusinessStepDetail; +import org.apache.fineract.client.models.ConfiguredJobNamesDTO; +import org.apache.fineract.client.models.JobBusinessStepConfigData; +import org.apache.fineract.client.models.JobBusinessStepDetail; +import org.apache.fineract.test.helper.WorkFlowJobHelper; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public class BusinessStepConfigurationStepDef extends AbstractStepDef { + + @Autowired + private WorkFlowJobHelper workFlowJobHelper; + + @Then("Admin checks that configured business jobs contain {string}") + public void checkConfiguredBusinessJobsContain(String jobName) { + ConfiguredJobNamesDTO response = workFlowJobHelper.getConfiguredBusinessJobs(); + List businessJobs = response.getBusinessJobs(); + log.debug("Configured business jobs: {}", businessJobs); + assertThat(businessJobs)// + .as("Configured business jobs should contain '%s' but got: %s", jobName, businessJobs)// + .contains(jobName); + } + + @Then("Admin verifies configured business steps for {string} match:") + public void verifyConfiguredBusinessStepsMatch(String jobName, DataTable dataTable) { + JobBusinessStepConfigData response = workFlowJobHelper.getConfiguredWorkflowSteps(jobName); + List actualSteps = response.getBusinessSteps(); + log.debug("Configured business steps for '{}': {}", jobName, actualSteps); + + List> expectedRows = dataTable.asMaps(String.class, String.class); + assertThat(actualSteps)// + .as("Configured steps count for '%s' — expected %d but got %d: %s", jobName, expectedRows.size(), actualSteps.size(), + actualSteps)// + .hasSize(expectedRows.size()); + + List sortedActual = actualSteps.stream()// + .sorted(Comparator.comparingLong(BusinessStep::getOrder))// + .toList(); + + for (int i = 0; i < expectedRows.size(); i++) { + Map expected = expectedRows.get(i); + BusinessStep actual = sortedActual.get(i); + String expectedStepName = expected.get("stepName"); + long expectedOrder = Long.parseLong(expected.get("order")); + assertThat(actual.getStepName())// + .as("Step at position %d for job '%s' — expected name '%s' but got '%s'", i, jobName, expectedStepName, + actual.getStepName())// + .isEqualTo(expectedStepName); + assertThat(actual.getOrder())// + .as("Step '%s' for job '%s' — expected order %d but got %d", expectedStepName, jobName, expectedOrder, + actual.getOrder())// + .isEqualTo(expectedOrder); + } + } + + @Then("Admin verifies available business steps for {string} contain:") + public void verifyAvailableBusinessStepsContain(String jobName, DataTable dataTable) { + JobBusinessStepDetail response = workFlowJobHelper.getAvailableWorkflowSteps(jobName); + List actualStepNames = response.getAvailableBusinessSteps().stream()// + .map(BusinessStepDetail::getStepName)// + .toList(); + log.debug("Available business steps for '{}': {}", jobName, actualStepNames); + + List> expectedRows = dataTable.asMaps(String.class, String.class); + for (Map row : expectedRows) { + String expectedStepName = row.get("stepName"); + assertThat(actualStepNames)// + .as("Available steps for '%s' should contain '%s' but got: %s", jobName, expectedStepName, actualStepNames)// + .contains(expectedStepName); + } + } + + @When("Admin updates business steps for {string} with:") + public void updateBusinessSteps(String jobName, DataTable dataTable) { + List> rows = dataTable.asMaps(String.class, String.class); + List steps = rows.stream()// + .map(row -> new BusinessStep()// + .stepName(row.get("stepName"))// + .order(Long.parseLong(row.get("order"))))// + .toList(); + log.debug("Updating business steps for '{}': {}", jobName, steps); + workFlowJobHelper.updateWorkflowSteps(jobName, steps); + } + + @Then("Admin fails to update business steps for {string} with invalid step {string}") + public void updateBusinessStepsFailsWithInvalidStep(String jobName, String invalidStepName) { + List steps = List.of(new BusinessStep().stepName(invalidStepName).order(1L)); + try { + workFlowJobHelper.updateWorkflowSteps(jobName, steps); + throw new AssertionError( + "Expected update to fail for invalid step '%s' on job '%s' but it succeeded".formatted(invalidStepName, jobName)); + } catch (CallFailedRuntimeException e) { + log.debug("Business step update correctly rejected for '{}' on job '{}': status={}", invalidStepName, jobName, e.getStatus()); + assertThat(e.getStatus())// + .as("Expected HTTP 400 for invalid step '%s' on job '%s' but got %d", invalidStepName, jobName, e.getStatus())// + .isEqualTo(400); + } + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/ClientStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/ClientStepDef.java index 1ed20013918..47d2fea2274 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/ClientStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/ClientStepDef.java @@ -24,32 +24,42 @@ import io.cucumber.java.en.Then; import io.cucumber.java.en.When; import java.util.Collections; +import lombok.RequiredArgsConstructor; import org.apache.fineract.client.feign.FineractFeignClient; import org.apache.fineract.client.models.ClientAddressRequest; import org.apache.fineract.client.models.PostClientsRequest; import org.apache.fineract.client.models.PostClientsResponse; +import org.apache.fineract.client.models.PostOfficesResponse; import org.apache.fineract.test.factory.ClientRequestFactory; import org.apache.fineract.test.messaging.event.EventCheckHelper; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; -import org.springframework.beans.factory.annotation.Autowired; +@RequiredArgsConstructor public class ClientStepDef extends AbstractStepDef { - @Autowired - private FineractFeignClient fineractClient; - - @Autowired - private ClientRequestFactory clientRequestFactory; - - @Autowired - private EventCheckHelper eventCheckHelper; + private final FineractFeignClient fineractClient; + private final ClientRequestFactory clientRequestFactory; + private final EventCheckHelper eventCheckHelper; @When("Admin creates a client with random data") public void createClientRandomFirstNameLastName() { PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest(); - PostClientsResponse response = ok(() -> fineractClient.clients().create6(clientsRequest)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); + testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response); + + eventCheckHelper.clientEventCheck(response); + } + + @When("Admin creates a client with random data in the last created office") + public void createClientInLastCreatedOffice() { + final PostOfficesResponse officeResponse = testContext().get(TestContextKey.OFFICE_CREATE_RESPONSE); + assertThat(officeResponse).as("No office was created. Use 'Admin creates a new office' step first.").isNotNull(); + final PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest()// + .officeId(officeResponse.getOfficeId()); + + final PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response); eventCheckHelper.clientEventCheck(response); @@ -59,7 +69,7 @@ public void createClientRandomFirstNameLastName() { public void createSecondClientRandomFirstNameLastName() { PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest(); - PostClientsResponse response = ok(() -> fineractClient.clients().create6(clientsRequest)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); testContext().set(TestContextKey.CLIENT_CREATE_SECOND_CLIENT_RESPONSE, response); eventCheckHelper.clientEventCheck(response); @@ -69,7 +79,7 @@ public void createSecondClientRandomFirstNameLastName() { public void createClient(String firstName, String lastName) { PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest().firstname(firstName).lastname(lastName); - PostClientsResponse response = ok(() -> fineractClient.clients().create6(clientsRequest)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response); } @@ -88,7 +98,7 @@ public void createClientWithAddress(String firstName, String lastName) { PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest().firstname(firstName).lastname(lastName) .address(Collections.singletonList(addressRequest)); - PostClientsResponse response = ok(() -> fineractClient.clients().create6(clientsRequest)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response); } @@ -99,7 +109,7 @@ public void createClientWithSpecifiedDates(String firstName, String lastName, St PostClientsRequest clientsRequest = clientRequestFactory.defaultClientCreationRequest().firstname(firstName).lastname(lastName) .activationDate(activationDate); - PostClientsResponse response = ok(() -> fineractClient.clients().create6(clientsRequest)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(clientsRequest)); testContext().set(TestContextKey.CLIENT_CREATE_RESPONSE, response); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/CurrencyStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/CurrencyStepDef.java new file mode 100644 index 00000000000..ddba1291228 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/CurrencyStepDef.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.common; + +import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.CurrencyConfigurationData; +import org.apache.fineract.client.models.CurrencyData; +import org.apache.fineract.client.models.CurrencyUpdateRequest; +import org.apache.fineract.client.models.CurrencyUpdateResponse; +import org.apache.fineract.test.helper.ErrorMessageHelper; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; + +@RequiredArgsConstructor +public class CurrencyStepDef extends AbstractStepDef { + + private static final List DEFAULT_CURRENCIES = Arrays.asList("EUR", "USD"); + private final FineractFeignClient fineractClient; + + @When("Admin retrieves currency configuration") + public void adminRetrievesCurrencyConfiguration() { + + CurrencyConfigurationData response = ok(() -> fineractClient.currency().retrieveCurrencies()); + testContext().set(TestContextKey.GET_CURRENCIES_RESPONSE, response); + + } + + @Then("Currency {string} has the following properties:") + public void currencyHasFollowingProperties(String currencyCode, DataTable table) { + + CurrencyConfigurationData config = testContext().get(TestContextKey.GET_CURRENCIES_RESPONSE); + + List allCurrencies = new ArrayList<>(); + if (config.getSelectedCurrencyOptions() != null) { + allCurrencies.addAll(config.getSelectedCurrencyOptions()); + } + if (config.getCurrencyOptions() != null) { + allCurrencies.addAll(config.getCurrencyOptions()); + } + + Map expected = table.asMaps().get(0); + String expectedName = expected.get("name"); + String expectedSymbol = expected.get("symbol"); + int expectedDecimalPlaces = Integer.parseInt(expected.get("decimalPlaces")); + + CurrencyData currency = allCurrencies.stream().filter(c -> currencyCode.equals(c.getCode())).findFirst().orElse(null); + + assertThat(currency).as(ErrorMessageHelper.currencyNotFound(currencyCode)).isNotNull(); + assertThat(currency.getName()).as(ErrorMessageHelper.wrongCurrencyField(currencyCode, "name", currency.getName(), expectedName)) + .isEqualTo(expectedName); + assertThat(currency.getDisplaySymbol()) + .as(ErrorMessageHelper.wrongCurrencyField(currencyCode, "displaySymbol", currency.getDisplaySymbol(), expectedSymbol)) + .isEqualTo(expectedSymbol); + assertThat(currency.getDecimalPlaces()).as( + ErrorMessageHelper.wrongCurrencyField(currencyCode, "decimalPlaces", currency.getDecimalPlaces(), expectedDecimalPlaces)) + .isEqualTo(expectedDecimalPlaces); + assertThat(currency.getNameCode()) + .as(ErrorMessageHelper.wrongCurrencyField(currencyCode, "nameCode", currency.getNameCode(), "currency." + currencyCode)) + .isEqualTo("currency." + currencyCode); + assertThat(currency.getDisplayLabel()).as(ErrorMessageHelper.wrongCurrencyField(currencyCode, "displayLabel", + currency.getDisplayLabel(), expectedName + " (" + expectedSymbol + ")")) + .isEqualTo(expectedName + " (" + expectedSymbol + ")"); + + } + + @When("Admin updates selected currencies to {string}") + public void adminUpdatesSelectedCurrencies(String currencyCodes) { + + List currencies = Arrays.asList(currencyCodes.split(",")); + CurrencyUpdateRequest request = new CurrencyUpdateRequest().currencies(currencies); + CurrencyUpdateResponse response = ok(() -> fineractClient.currency().updateCurrencies(request, Map.of())); + testContext().set(TestContextKey.PUT_CURRENCIES_RESPONSE, response); + + } + + @Then("The returned currency list matches {string}") + public void returnedCurrencyListMatches(String expectedCodes) { + + List expected = Arrays.asList(expectedCodes.split(",")); + CurrencyUpdateResponse response = testContext().get(TestContextKey.PUT_CURRENCIES_RESPONSE); + + assertThat(response).as(ErrorMessageHelper.idNull()).isNotNull(); + assertThat(response.getCurrencies()).as(ErrorMessageHelper.wrongSelectedCurrencies(response.getCurrencies(), expected)) + .isEqualTo(expected); + + } + + @Then("The selected currencies contain {string}") + public void selectedCurrenciesContain(String expectedCodes) { + + List expected = Arrays.asList(expectedCodes.split(",")); + CurrencyConfigurationData config = testContext().get(TestContextKey.GET_CURRENCIES_RESPONSE); + + assertThat(config.getSelectedCurrencyOptions()).as(ErrorMessageHelper.idNull()).isNotNull(); + + List selectedCodes = config.getSelectedCurrencyOptions().stream().map(CurrencyData::getCode).sorted().toList(); + List expectedSorted = expected.stream().sorted().toList(); + + assertThat(selectedCodes).as(ErrorMessageHelper.wrongSelectedCurrencies(selectedCodes, expectedSorted)).isEqualTo(expectedSorted); + + } + + @And("Admin resets selected currencies to default") + public void adminResetsSelectedCurrenciesToDefault() { + + CurrencyUpdateRequest request = new CurrencyUpdateRequest().currencies(DEFAULT_CURRENCIES); + ok(() -> fineractClient.currency().updateCurrencies(request, Map.of())); + + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java index 88ea76f9928..3f2b38a68a8 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/GlobalConfigurationStepDef.java @@ -60,7 +60,7 @@ public void setGlobalConfigValueString(String configKey, String configValue) { @When("Global config {string} value set to {string} through DefaultApi") public void setGlobalConfigValueStringDefaultApi(String configKey, String configValue) { Long configValueLong = Long.valueOf(configValue); - fineractClient.defaultApi().updateGlobalConfiguration(configKey, configValueLong); + fineractClient.defaultApi().updateInternalGlobalConfiguration(configKey, configValueLong); } @When("Update currency with incorrect empty value outcomes with an error") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java index 41c9feb94bd..a8d7ce07bc4 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java @@ -172,7 +172,7 @@ public List> getJournalLinesActualList(List journalQueryParams = new HashMap<>(); journalQueryParams.put("transactionId", transactionId); journalQueryParams.put("runningBalance", true); - journalEntryDataResponse = journalEntriesApi().retrieveAll1(journalQueryParams); + journalEntryDataResponse = journalEntriesApi().retrieveAllJournalEntries(journalQueryParams); } catch (Exception e) { log.error("Exception", e); } @@ -222,7 +222,7 @@ public void capitalizedIncomeAmortizationJournalEntryDataCheck(final DataTable t journalQueryParams.put("transactionId", transactionId); journalQueryParams.put("loanId", loanId); journalQueryParams.put("runningBalance", true); - journalEntryDataResponse = journalEntriesApi().retrieveAll1(journalQueryParams); + journalEntryDataResponse = journalEntriesApi().retrieveAllJournalEntries(journalQueryParams); } catch (Exception e) { log.error("Exception", e); } @@ -297,7 +297,7 @@ public void revertedJournalEntryDataCheck(String transactionType, String transac Map journalQueryParams = new HashMap<>(); journalQueryParams.put("transactionId", t); journalQueryParams.put("runningBalance", true); - journalEntryDataResponse = journalEntriesApi().retrieveAll1(journalQueryParams); + journalEntryDataResponse = journalEntriesApi().retrieveAllJournalEntries(journalQueryParams); } catch (Exception e) { log.error("Exception", e); } @@ -364,7 +364,7 @@ public void journalEntryNoDataCheck(String transactionType, String transactionDa Map journalQueryParams = new HashMap<>(); journalQueryParams.put("transactionId", transactionId); journalQueryParams.put("runningBalance", true); - journalEntryDataResponse = journalEntriesApi().retrieveAll1(journalQueryParams); + journalEntryDataResponse = journalEntriesApi().retrieveAllJournalEntries(journalQueryParams); } catch (Exception e) { log.error("Exception", e); } @@ -436,7 +436,7 @@ public void checkManualJournalEntry(String externalAssetOwnerEnabled, DataTable Map journalQueryParams = new HashMap<>(); journalQueryParams.put("transactionId", transactionId); journalQueryParams.put("runningBalance", true); - journalEntryDataResponse = journalEntriesApi().retrieveAll1(journalQueryParams); + journalEntryDataResponse = journalEntriesApi().retrieveAllJournalEntries(journalQueryParams); } catch (Exception e) { log.error("Exception", e); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/OfficeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/OfficeStepDef.java new file mode 100644 index 00000000000..94215d5a4c2 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/OfficeStepDef.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.common; + +import static org.apache.fineract.client.feign.util.FeignCalls.ok; + +import io.cucumber.java.en.When; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.PostOfficesRequest; +import org.apache.fineract.client.models.PostOfficesResponse; +import org.apache.fineract.test.helper.Utils; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; + +@RequiredArgsConstructor +public class OfficeStepDef extends AbstractStepDef { + + private final FineractFeignClient fineractClient; + + @When("Admin creates a new office") + public void createNewOffice() { + final PostOfficesRequest request = new PostOfficesRequest()// + .name(Utils.randomStringGenerator("Office_", 5))// + .parentId(1L)// + .openingDate(LocalDate.of(2000, 1, 1))// + .dateFormat("yyyy-MM-dd")// + .locale("en");// + + final PostOfficesResponse response = ok(() -> fineractClient.offices().createOffice(request)); + testContext().set(TestContextKey.OFFICE_CREATE_RESPONSE, response); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java index f8baa6b6001..5b19f9ac15c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/SchedulerStepDef.java @@ -18,9 +18,16 @@ */ package org.apache.fineract.test.stepdef.common; +import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import static org.assertj.core.api.Assertions.assertThat; + import io.cucumber.java.en.And; +import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.GetJobsResponse; import org.apache.fineract.test.data.job.DefaultJob; import org.apache.fineract.test.service.JobService; import org.apache.fineract.test.stepdef.AbstractStepDef; @@ -32,6 +39,9 @@ public class SchedulerStepDef extends AbstractStepDef { @Autowired private JobService jobService; + @Autowired + private FineractFeignClient fineractClient; + @And("Admin runs the Add Accrual Transactions job") public void runAccrualTransaction() { jobService.executeAndWait(DefaultJob.ADD_ACCRUAL_TRANSACTIONS); @@ -71,4 +81,29 @@ public void runCOB() { public void runAccrualActivityPosting() { jobService.executeAndWait(DefaultJob.ACCRUAL_ACTIVITY_POSTING); } + + @When("Admin runs WC COB job") + public void runWorkingCapitalLoanCOB() { + jobService.executeAndWait(DefaultJob.WORKING_CAPITAL_LOAN_COB); + } + + @Then("Admin verifies scheduler job {string} has display name {string}") + public void verifyJobDisplayName(String shortName, String expectedDisplayName) { + GetJobsResponse response = ok(() -> fineractClient.schedulerJob().retrieveByShortName(shortName, Map.of())); + assertThat(response.getDisplayName())// + .as("Job '%s' display name — expected '%s' but got '%s'", shortName, expectedDisplayName, response.getDisplayName())// + .isEqualTo(expectedDisplayName); + } + + @Then("Admin verifies scheduler job {string} has active status {string}") + public void verifyJobActiveStatus(String shortName, String expectedActive) { + assertThat(expectedActive)// + .as("Parameter must be 'true' or 'false' but got '%s'", expectedActive)// + .isIn("true", "false"); + GetJobsResponse response = ok(() -> fineractClient.schedulerJob().retrieveByShortName(shortName, Map.of())); + boolean expected = Boolean.parseBoolean(expectedActive); + assertThat(response.getActive())// + .as("Job '%s' active status — expected %s but got %s", shortName, expected, response.getActive())// + .isEqualTo(expected); + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/UserStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/UserStepDef.java index 03da79e25ef..0a2ccacd9a9 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/UserStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/UserStepDef.java @@ -68,7 +68,7 @@ public void createUserWithUsernameAndRoles(String username, String roleName, Lis .repeatPassword(PWD_USER_WITH_ROLE) // .roles(List.of(roleId)); - PostUsersResponse createUserResponse = ok(() -> fineractClient.users().create16(postUsersRequest)); + PostUsersResponse createUserResponse = ok(() -> fineractClient.users().createUser(postUsersRequest)); testContext().set(TestContextKey.CREATED_SIMPLE_USER_RESPONSE, createUserResponse); testContext().set(TestContextKey.CREATED_SIMPLE_USER_USERNAME, generatedUsername); testContext().set(TestContextKey.CREATED_SIMPLE_USER_PASSWORD, PWD_USER_WITH_ROLE); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/WorkingCapitalLoanCobStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/WorkingCapitalLoanCobStepDef.java new file mode 100644 index 00000000000..c6a51ac6654 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/WorkingCapitalLoanCobStepDef.java @@ -0,0 +1,259 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.common; + +import static org.apache.fineract.client.feign.util.FeignCalls.executeVoid; +import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import io.cucumber.java.After; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.BusinessDateResponse; +import org.apache.fineract.client.models.InlineJobRequest; +import org.apache.fineract.client.models.IsCatchUpRunningDTO; +import org.apache.fineract.client.models.OldestCOBProcessedLoanDTO; +import org.apache.fineract.test.data.LoanStatus; +import org.apache.fineract.test.helper.BusinessDateHelper; +import org.apache.fineract.test.helper.WorkingCapitalLoanTestHelper; +import org.apache.fineract.test.messaging.config.JobPollingProperties; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public class WorkingCapitalLoanCobStepDef extends AbstractStepDef { + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd MMMM yyyy"); + + @Autowired + private WorkingCapitalLoanTestHelper wcLoanHelper; + @Autowired + private FineractFeignClient fineractClient; + @Autowired + private JobPollingProperties jobPollingProperties; + + @Before(value = "@WCCOBFeature") + public void beforeWcCobScenario() { + testContext().set(TestContextKey.WC_LOAN_IDS, new ArrayList()); + } + + // order > 10000 (default) so this cleanup runs before other @After hooks that may depend on DB state + @After(value = "@WCCOBFeature", order = 10001) + public void afterWcCobScenario() { + List loanIds = getTrackedLoanIds(); + if (!loanIds.isEmpty()) { + log.debug("After hook: cleaning up {} WC loan(s)", loanIds.size()); + for (Long loanId : loanIds) { + try { + wcLoanHelper.deleteById(loanId); + log.debug("After hook: deleted WC loan id={}", loanId); + } catch (Exception e) { + log.warn("After hook: failed to delete WC loan id={}: {}", loanId, e.getMessage()); + } + } + loanIds.clear(); + } + } + + @When("Admin runs inline COB job for Working Capital Loan") + public void runWorkingCapitalInlineCOB() throws IOException { + InlineJobRequest inlineJobRequest = new InlineJobRequest().addLoanIdsItem(getTrackedLoanIds().getLast()); + ok(() -> fineractClient.inlineJob().executeInlineJob("WC_LOAN_COB", inlineJobRequest)); + } + + @When("Admin runs inline COB job for all Working Capital Loans") + public void runWorkingCapitalInlineCOBForAll() throws IOException { + InlineJobRequest inlineJobRequest = new InlineJobRequest(); + for (Long loanId : getTrackedLoanIds()) { + inlineJobRequest.addLoanIdsItem(loanId); + } + ok(() -> fineractClient.inlineJob().executeInlineJob("WC_LOAN_COB", inlineJobRequest)); + } + + @Given("Admin inserts an active WC loan into the database") + public void insertActiveWcLoan() { + Long loanId = wcLoanHelper.insertActiveLoan(); + log.debug("Inserted WC loan with id={}", loanId); + getTrackedLoanIds().add(loanId); + } + + @Given("Admin inserts {int} active WC loans into the database") + public void insertMultipleActiveWcLoans(int count) { + for (int i = 0; i < count; i++) { + Long loanId = wcLoanHelper.insertActiveLoan(); + log.debug("Inserted WC loan with id={}", loanId); + getTrackedLoanIds().add(loanId); + } + } + + @Then("Admin verifies all inserted WC loans have lastClosedBusinessDate {string}") + public void verifyAllLoansHaveLastClosedBusinessDate(String expectedDate) { + LocalDate expected = LocalDate.parse(expectedDate, DATE_FORMAT); + List loanIds = getTrackedLoanIds(); + assertThat(loanIds).as("No WC loan IDs tracked in test context").isNotEmpty(); + for (Long loanId : loanIds) { + LocalDate actual = wcLoanHelper.getLastClosedBusinessDate(loanId); + log.debug("WC loan id={} lastClosedBusinessDate={}", loanId, actual); + assertThat(actual)// + .as("WC loan id=%d — expected lastClosedBusinessDate '%s' but got '%s'", loanId, expected, actual)// + .isEqualTo(expected); + } + } + + @Given("Admin inserts a WC loan with status {string} into the database") + public void insertWcLoanWithStatus(String statusName) { + LoanStatus status = LoanStatus.valueOf(statusName); + Long loanId = wcLoanHelper.insertLoan(status.getValue(), null); + log.debug("Inserted WC loan with id={}, status={}({})", loanId, statusName, status.getValue()); + getTrackedLoanIds().add(loanId); + } + + @Given("Admin inserts a WC loan with status {string} and lastClosedBusinessDate {string} into the database") + public void insertWcLoanWithStatusAndDate(String statusName, String dateStr) { + LoanStatus status = LoanStatus.valueOf(statusName); + LocalDate lastClosedBusinessDate = LocalDate.parse(dateStr, DATE_FORMAT); + Long loanId = wcLoanHelper.insertLoan(status.getValue(), lastClosedBusinessDate); + log.debug("Inserted WC loan with id={}, status={}({}), lastClosedBusinessDate={}", loanId, statusName, status.getValue(), + lastClosedBusinessDate); + getTrackedLoanIds().add(loanId); + } + + @Then("Admin verifies all inserted WC loans have null lastClosedBusinessDate") + public void verifyAllLoansHaveNullLastClosedBusinessDate() { + List loanIds = getTrackedLoanIds(); + assertThat(loanIds).as("No WC loan IDs tracked in test context").isNotEmpty(); + for (Long loanId : loanIds) { + LocalDate actual = wcLoanHelper.getLastClosedBusinessDate(loanId); + log.debug("WC loan id={} lastClosedBusinessDate={}", loanId, actual); + assertThat(actual)// + .as("WC loan id=%d — expected null lastClosedBusinessDate but got '%s'", loanId, actual)// + .isNull(); + } + } + + @Then("Admin verifies all inserted WC loans have version {int}") + public void verifyAllLoansHaveVersion(int expectedVersion) { + List loanIds = getTrackedLoanIds(); + assertThat(loanIds).as("No WC loan IDs tracked in test context").isNotEmpty(); + for (Long loanId : loanIds) { + int actual = wcLoanHelper.getVersion(loanId); + log.debug("WC loan id={} version={}", loanId, actual); + assertThat(actual)// + .as("WC loan id=%d — expected version %d but got %d", loanId, expectedVersion, actual)// + .isEqualTo(expectedVersion); + } + } + + @Then("Admin verifies all inserted WC loans have no account locks") + public void verifyAllLoansHaveNoAccountLocks() { + List loanIds = getTrackedLoanIds(); + assertThat(loanIds).as("No WC loan IDs tracked in test context").isNotEmpty(); + for (Long loanId : loanIds) { + int lockCount = wcLoanHelper.countLocksByLoanId(loanId); + log.debug("WC loan id={} lock count={}", loanId, lockCount); + assertThat(lockCount)// + .as("WC loan id=%d — expected 0 account locks but got %d", loanId, lockCount)// + .isZero(); + } + } + + @Then("Admin verifies inserted WC loan {int} has lastClosedBusinessDate {string}") + public void verifyLoanAtIndexHasLastClosedBusinessDate(int index, String expectedDate) { + LocalDate expected = LocalDate.parse(expectedDate, DATE_FORMAT); + List loanIds = getTrackedLoanIds(); + assertThat(index).as("Loan index %d out of range (1..%d)", index, loanIds.size()).isBetween(1, loanIds.size()); + Long loanId = loanIds.get(index - 1); + LocalDate actual = wcLoanHelper.getLastClosedBusinessDate(loanId); + log.debug("WC loan index={} id={} lastClosedBusinessDate={}", index, loanId, actual); + assertThat(actual)// + .as("WC loan index=%d id=%d — expected lastClosedBusinessDate '%s' but got '%s'", index, loanId, expected, actual)// + .isEqualTo(expected); + } + + @Then("Admin verifies inserted WC loan {int} has null lastClosedBusinessDate") + public void verifyLoanAtIndexHasNullLastClosedBusinessDate(int index) { + List loanIds = getTrackedLoanIds(); + assertThat(index).as("Loan index %d out of range (1..%d)", index, loanIds.size()).isBetween(1, loanIds.size()); + Long loanId = loanIds.get(index - 1); + LocalDate actual = wcLoanHelper.getLastClosedBusinessDate(loanId); + log.debug("WC loan index={} id={} lastClosedBusinessDate={}", index, loanId, actual); + assertThat(actual)// + .as("WC loan index=%d id=%d — expected null lastClosedBusinessDate but got '%s'", index, loanId, actual)// + .isNull(); + } + + @When("Admin runs Working Capital COB catch up") + public void runWorkingCapitalLoanCOBCatchUp() { + try { + executeVoid(() -> fineractClient.workingCapitalLoanCobCatchUpApi().executeLoanCOBCatchUp1()); + } catch (CallFailedRuntimeException e) { + if (e.getStatus() == 400) { + log.info("COB catch-up is already running (400 response), continuing with test"); + } else { + throw e; + } + } + } + + @When("Admin checks that WC Loan COB is running until the current business date") + public void checkWCLoanCOBCatchUpRunningUntilCOBBusinessDate() { + await().atMost(Duration.ofMillis(jobPollingProperties.getTimeoutInMillis())) // + .pollInterval(Duration.ofMillis(jobPollingProperties.getIntervalInMillis())) // + .until(() -> { + IsCatchUpRunningDTO isCatchUpRunningResponse = ok( + () -> fineractClient.workingCapitalLoanCobCatchUpApi().isCatchUpRunning1()); + return isCatchUpRunningResponse.getCatchUpRunning(); + }); + // Then wait for catch-up to complete + await().atMost(Duration.ofMinutes(4)).pollInterval(Duration.ofSeconds(5)).pollDelay(Duration.ofSeconds(5)).until(() -> { + IsCatchUpRunningDTO statusResponse = ok(() -> fineractClient.workingCapitalLoanCobCatchUpApi().isCatchUpRunning1()); + if (!statusResponse.getCatchUpRunning()) { + BusinessDateResponse businessDateResponse = ok( + () -> fineractClient.businessDateManagement().getBusinessDate(BusinessDateHelper.COB, Map.of())); + LocalDate currentBusinessDate = businessDateResponse.getDate(); + + OldestCOBProcessedLoanDTO catchUpResponse = ok( + () -> fineractClient.workingCapitalLoanCobCatchUpApi().getOldestCOBProcessedLoan1()); + LocalDate lastClosedDate = catchUpResponse.getCobBusinessDate(); + + return !lastClosedDate.isBefore(currentBusinessDate); + } + return false; + }); + } + + @SuppressWarnings("unchecked") + private List getTrackedLoanIds() { + return testContext().get(TestContextKey.WC_LOAN_IDS); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java index 6d5d4035f01..dda5777b467 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java @@ -92,7 +92,7 @@ public void loanChargeAdjustmentFailedOnWrongAmount(String chargeTypeEnum, Strin String developerMessageExpected = "Transaction amount cannot be higher than the available charge amount for adjustment: 7.000000"; try { - fineractClient.loanCharges().executeLoanCharge2(loanId, transactionId, chargeAdjustmentRequest, + fineractClient.loanCharges().executeLoanChargeOnExistingCharge(loanId, transactionId, chargeAdjustmentRequest, Map.of("command", "adjustment")); throw new AssertionError("Expected FeignException but request succeeded"); } catch (FeignException e) { @@ -165,8 +165,9 @@ private void makeChargeAdjustmentCall(Long loanId, Long transactionId, String ex PostLoansLoanIdChargesChargeIdRequest chargeAdjustmentRequest = LoanRequestFactory.defaultChargeAdjustmentRequest() .amount(transactionAmount).externalId(externalId); - PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = ok(() -> fineractClient.loanCharges().executeLoanCharge2(loanId, - transactionId, chargeAdjustmentRequest, Map.of("command", "adjustment"))); + PostLoansLoanIdChargesChargeIdResponse chargeAdjustmentResponse = ok( + () -> fineractClient.loanCharges().executeLoanChargeOnExistingCharge(loanId, transactionId, chargeAdjustmentRequest, + Map.of("command", "adjustment"))); testContext().set(TestContextKey.LOAN_CHARGE_ADJUSTMENT_RESPONSE, chargeAdjustmentResponse); eventCheckHelper.loanBalanceChangedEventCheck(loanId); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java index 0cf4a19e871..385b52ad0df 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeStepDef.java @@ -206,6 +206,20 @@ public void addProcessingFee(double chargeAmount, String locale, String date) th eventCheckHelper.loanBalanceChangedEventCheck(loanId); } + @And("Admin adds a {double} % Processing charge to the loan with {string} locale on date: {string} - no event") + public void addProcessingFeeNoEvent(double chargeAmount, String locale, String date) throws IOException { + eventStore.reset(); + PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.getLoanId(); + PostLoansLoanIdChargesRequest loanIdChargesRequest = LoanChargeRequestFactory.defaultLoanChargeRequest() + .chargeId(ChargeProductType.LOAN_PERCENTAGE_PROCESSING_FEE.value).amount(chargeAmount).dueDate(date) + .dateFormat(DEFAULT_DATE_FORMAT).locale(locale); + + PostLoansLoanIdChargesResponse loanChargeResponse = ok( + () -> fineractClient.loanCharges().executeLoanCharge(loanId, loanIdChargesRequest, Map.of())); + testContext().set(TestContextKey.ADD_PROCESSING_FEE_RESPONSE, loanChargeResponse); + } + @And("Admin adds an NSF fee because of payment bounce with {string} transaction date") public void addNSFfee(String date) throws IOException { eventStore.reset(); @@ -231,8 +245,8 @@ public void waiveCharge() throws IOException { PostLoansLoanIdChargesChargeIdRequest waiveRequest = new PostLoansLoanIdChargesChargeIdRequest(); - PostLoansLoanIdChargesChargeIdResponse waiveResponse = ok(() -> fineractClient.loanCharges().executeLoanCharge2(loanId, chargeId, - waiveRequest, Map.of("command", "waive"))); + PostLoansLoanIdChargesChargeIdResponse waiveResponse = ok(() -> fineractClient.loanCharges() + .executeLoanChargeOnExistingCharge(loanId, chargeId, waiveRequest, Map.of("command", "waive"))); testContext().set(TestContextKey.WAIVE_CHARGE_RESPONSE, waiveResponse); } @@ -246,8 +260,8 @@ public void waiveDueDateCharge() throws IOException { PostLoansLoanIdChargesChargeIdRequest waiveRequest = new PostLoansLoanIdChargesChargeIdRequest(); - PostLoansLoanIdChargesChargeIdResponse waiveResponse = ok(() -> fineractClient.loanCharges().executeLoanCharge2(loanId, chargeId, - waiveRequest, Map.of("command", "waive"))); + PostLoansLoanIdChargesChargeIdResponse waiveResponse = ok(() -> fineractClient.loanCharges() + .executeLoanChargeOnExistingCharge(loanId, chargeId, waiveRequest, Map.of("command", "waive"))); testContext().set(TestContextKey.WAIVE_CHARGE_RESPONSE, waiveResponse); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java index 23e9735751c..975979a535a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java @@ -277,7 +277,7 @@ public void delinquencyPauseByLoanExternalId(String startDate, String endDate) { .locale(DEFAULT_LOCALE);// PostLoansDelinquencyActionResponse response = ok( - () -> fineractClient.loans().createLoanDelinquencyAction1(loanExternalId, request)); + () -> fineractClient.loans().createLoanDelinquencyActionByExternalId(loanExternalId, request)); testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response); eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId); } @@ -295,7 +295,7 @@ public void delinquencyResumeByLoanExternalId(String startDate) { .locale(DEFAULT_LOCALE);// PostLoansDelinquencyActionResponse response = ok( - () -> fineractClient.loans().createLoanDelinquencyAction1(loanExternalId, request)); + () -> fineractClient.loans().createLoanDelinquencyActionByExternalId(loanExternalId, request)); testContext().set(TestContextKey.LOAN_DELINQUENCY_ACTION_RESPONSE, response); eventCheckHelper.loanAccountDelinquencyPauseChangedBusinessEventCheck(loanId); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOriginationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOriginationStepDef.java index 1ade03c99b5..9bab16f1573 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOriginationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOriginationStepDef.java @@ -109,7 +109,7 @@ public void createOriginatorWithAllFields(String name) { PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(externalId).name(name) .originatorTypeId(originatorType.getId()).channelTypeId(channelType.getId()); - PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().create11(request)); + PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().createLoanOriginator(request)); assertThat(response.getResourceId()).isNotNull(); testContext().set(TestContextKey.ORIGINATOR_CREATE_RESPONSE, response); @@ -131,7 +131,7 @@ public void verifyOriginatorStatus(String expectedStatus) { PostLoanOriginatorsResponse createResponse = testContext().get(TestContextKey.ORIGINATOR_CREATE_RESPONSE); Long originatorId = createResponse.getResourceId(); - GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); assertThat(originator.getStatus()).isEqualTo(expectedStatus); assertThat(originator.getExternalId()).isEqualTo(testContext().get(TestContextKey.ORIGINATOR_EXTERNAL_ID)); @@ -145,7 +145,7 @@ public void verifyOriginatorAllFields() { String expectedOriginatorTypeName = testContext().get(TestContextKey.ORIGINATOR_TYPE_NAME); String expectedChannelTypeName = testContext().get(TestContextKey.ORIGINATOR_CHANNEL_TYPE_NAME); - GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); assertThat(originator.getId()).as("Originator ID").isNotNull(); assertThat(originator.getExternalId()).as("Originator externalId").isEqualTo(expectedExternalId); @@ -216,7 +216,7 @@ public void verifyOriginatorWithAllFieldsInLoanDetails(String association) { // Verify type fields via direct originator GET (loan details serializes CodeValueData as nested objects // which don't map to the flat fields in the generated client model) - GetLoanOriginatorsResponse originatorDetails = ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + GetLoanOriginatorsResponse originatorDetails = ok(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); assertThat(originatorDetails.getOriginatorType()).as("Originator type").isNotNull(); assertThat(originatorDetails.getOriginatorType().getName()).as("Originator type name").isEqualTo(expectedOriginatorTypeName); assertThat(originatorDetails.getChannelType()).as("Channel type").isNotNull(); @@ -335,7 +335,7 @@ public void attachOriginatorToNonExistentLoanShouldFail(int expectedStatus) { public void createOriginatorWithoutNameShouldFail(int expectedStatus) { PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(UUID.randomUUID().toString()); - CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().create11(request)); + CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().createLoanOriginator(request)); assertExpectedStatus(exception, expectedStatus); log.info("Create originator without name failed with expected status {}", expectedStatus); } @@ -344,7 +344,7 @@ public void createOriginatorWithoutNameShouldFail(int expectedStatus) { public void createOriginatorWithoutNameSucceeds() { PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(UUID.randomUUID().toString()); - PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().create11(request)); + PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().createLoanOriginator(request)); assertThat(response.getResourceId()).as("Originator created without name").isNotNull(); log.info("Created originator without name, resourceId {}", response.getResourceId()); } @@ -382,7 +382,7 @@ public void userWithoutCreatePermissionFails() { PostLoanOriginatorsRequest request = new PostLoanOriginatorsRequest().externalId(UUID.randomUUID().toString()) .name("Should Fail Originator"); - CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().create11(request)); + CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().createLoanOriginator(request)); assertExpectedStatus(exception, 403); log.info("User without CREATE_LOAN_ORIGINATOR permission failed to create originator as expected"); } @@ -394,7 +394,7 @@ public void userWithoutUpdatePermissionFails() { PutLoanOriginatorsRequest updateRequest = new PutLoanOriginatorsRequest().name("Should Fail Update"); - CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().update16(originatorId, updateRequest)); + CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().updateLoanOriginator(originatorId, updateRequest)); assertExpectedStatus(exception, 403); log.info("User without UPDATE_LOAN_ORIGINATOR permission failed to update originator {} as expected", originatorId); } @@ -404,7 +404,7 @@ public void userWithoutDeletePermissionFails() { long originatorId = getOriginatorId(); FineractFeignClient userClient = createClientForUser(); - CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().delete14(originatorId)); + CallFailedRuntimeException exception = fail(() -> userClient.loanOriginators().deleteLoanOriginator(originatorId)); assertExpectedStatus(exception, 403); log.info("User without DELETE_LOAN_ORIGINATOR permission failed to delete originator {} as expected", originatorId); } @@ -487,7 +487,7 @@ public void retrieveOriginatorByExternalIdWithAllFields() { public void verifyOriginatorInList() { long expectedId = getOriginatorId(); - List allOriginators = ok(() -> fineractClient.loanOriginators().retrieveAll28()); + List allOriginators = ok(() -> fineractClient.loanOriginators().retrieveAllLoanOriginators()); assertThat(allOriginators).as("Originator list should not be null or empty").isNotNull().isNotEmpty(); @@ -515,7 +515,8 @@ public void updateOriginatorById(String newName, String newStatus) { PutLoanOriginatorsRequest updateRequest = new PutLoanOriginatorsRequest().name(newName).status(newStatus); - PutLoanOriginatorsResponse updateResponse = ok(() -> fineractClient.loanOriginators().update16(originatorId, updateRequest)); + PutLoanOriginatorsResponse updateResponse = ok( + () -> fineractClient.loanOriginators().updateLoanOriginator(originatorId, updateRequest)); assertThat(updateResponse.getResourceId()).as("Updated originator resource ID").isEqualTo(originatorId); log.info("Updated originator {} with name={}, status={}", originatorId, newName, newStatus); @@ -538,7 +539,7 @@ public void updateOriginatorByExternalId(String newName) { public void verifyOriginatorNameAndStatus(String expectedName, String expectedStatus) { long originatorId = getOriginatorId(); - GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + GetLoanOriginatorsResponse originator = ok(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); assertThat(originator.getName()).as("Originator name").isEqualTo(expectedName); assertThat(originator.getStatus()).as("Originator status").isEqualTo(expectedStatus); @@ -559,7 +560,7 @@ public void verifyOriginatorByExternalIdHasName(String expectedName) { public void deleteOriginatorById() { long originatorId = getOriginatorId(); - DeleteLoanOriginatorsResponse deleteResponse = ok(() -> fineractClient.loanOriginators().delete14(originatorId)); + DeleteLoanOriginatorsResponse deleteResponse = ok(() -> fineractClient.loanOriginators().deleteLoanOriginator(originatorId)); assertThat(deleteResponse.getResourceId()).as("Deleted originator resource ID").isEqualTo(originatorId); log.info("Deleted originator by ID {}", originatorId); @@ -579,7 +580,7 @@ public void deleteOriginatorByExternalId() { public void retrieveDeletedOriginatorByIdShouldFail(int expectedStatus) { long originatorId = getOriginatorId(); - CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); assertExpectedStatus(exception, expectedStatus); log.info("Retrieving deleted originator {} failed with expected status {}", originatorId, expectedStatus); } @@ -597,7 +598,7 @@ public void retrieveDeletedOriginatorByExternalIdShouldFail(int expectedStatus) public void deleteOriginatorShouldFailWithStatus(int expectedStatus) { long originatorId = getOriginatorId(); - CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().delete14(originatorId)); + CallFailedRuntimeException exception = fail(() -> fineractClient.loanOriginators().deleteLoanOriginator(originatorId)); assertExpectedStatus(exception, expectedStatus); log.info("Deleting originator {} failed with expected status {}", originatorId, expectedStatus); } @@ -621,7 +622,7 @@ private void createOriginatorAndStore(String name, String status, String respons request.status(status); } - PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().create11(request)); + PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().createLoanOriginator(request)); assertThat(response.getResourceId()).isNotNull(); testContext().set(responseKey, response); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java index d70acbe9a30..3f46a3f5685 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java @@ -92,8 +92,8 @@ public void createReAgingTransactionByLoanExternalId(DataTable table) { table.row(1) // ); - PostLoansLoanIdTransactionsResponse response = ok(() -> fineractClient.loanTransactions().executeLoanTransaction1(loanExternalId, - reAgingRequest, Map.of("command", "reAge"))); + PostLoansLoanIdTransactionsResponse response = ok(() -> fineractClient.loanTransactions() + .executeLoanTransactionByLoanExternalId(loanExternalId, reAgingRequest, Map.of("command", "reAge"))); testContext().set(TestContextKey.LOAN_REAGING_RESPONSE, response); } @@ -171,7 +171,8 @@ public LoanScheduleData reAgingPreviewByLoanExternalId(DataTable table) { String loanExternalId = loanResponse.getResourceExternalId(); Map queryParams = resolveReAgingQueryParams(table); - LoanScheduleData result = ok(() -> fineractClient.loanTransactions().previewReAgeSchedule1(loanExternalId, queryParams)); + LoanScheduleData result = ok( + () -> fineractClient.loanTransactions().previewReAgeScheduleByLoanExternalId(loanExternalId, queryParams)); log.info("Re-aging preview is requested to be created with loan external ID: {} with parameters: {}", loanExternalId, queryParams); return result; } @@ -191,7 +192,7 @@ public void reAgePreviewChargedOffLoanFailure(final DataTable table) { Map queryParams = resolveReAgingQueryParams(table); CallFailedRuntimeException exception = fail( - () -> fineractClient.loanTransactions().previewReAgeSchedule1(loanExternalId, queryParams)); + () -> fineractClient.loanTransactions().previewReAgeScheduleByLoanExternalId(loanExternalId, queryParams)); assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403); assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.reAgeChargedOffLoanFailure()); @@ -205,7 +206,7 @@ public void reAgePreviewContractTerminatedLoanFailure(final DataTable table) { Map queryParams = resolveReAgingQueryParams(table); CallFailedRuntimeException exception = fail( - () -> fineractClient.loanTransactions().previewReAgeSchedule1(loanExternalId, queryParams)); + () -> fineractClient.loanTransactions().previewReAgeScheduleByLoanExternalId(loanExternalId, queryParams)); assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403); assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.reAgeContractTerminatedLoanFailure()); @@ -218,7 +219,7 @@ public void reAgePreviewClosedLoanFailure(final DataTable table) throws IOExcept Map queryParams = resolveReAgingQueryParams(table); CallFailedRuntimeException exception = fail( - () -> fineractClient.loanTransactions().previewReAgeSchedule1(loanExternalId, queryParams)); + () -> fineractClient.loanTransactions().previewReAgeScheduleByLoanExternalId(loanExternalId, queryParams)); assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403); assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.reAgeClosedLoanFailure()); @@ -366,7 +367,7 @@ private List validateRepaymentScheduleTotal(List header, LoanSch PostLoansLoanIdTransactionsRequest setReAgeingRequestProperties(PostLoansLoanIdTransactionsRequest request, List headers, List values) { for (int i = 0; i < headers.size(); i++) { - String header = headers.get(i).toLowerCase().trim().replaceAll(" ", ""); + String header = headers.get(i).toLowerCase(java.util.Locale.ROOT).trim().replaceAll(" ", ""); switch (header) { case "frequencynumber" -> request.setFrequencyNumber(Integer.parseInt(values.get(i))); case "frequencytype" -> request.setFrequencyType(values.get(i)); @@ -391,8 +392,8 @@ public void adminCreatesALoanReAgingTransactionByLoanExternalIDWithTheFollowingD table.row(1) // ); - CallFailedRuntimeException response = fail(() -> fineractClient.loanTransactions().executeLoanTransaction1(loanExternalId, - reAgingRequest, Map.of("command", "reAge"))); + CallFailedRuntimeException response = fail(() -> fineractClient.loanTransactions() + .executeLoanTransactionByLoanExternalId(loanExternalId, reAgingRequest, Map.of("command", "reAge"))); assertThat(response.getStatus()).isEqualTo(errorCode); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java index 54365c0963a..08540dbe392 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAmortizationStepDef.java @@ -102,8 +102,8 @@ public void createLoanReAmortizationByLoanExternalId() { PostLoansLoanIdTransactionsRequest reAmortizationRequest = LoanRequestFactory.defaultLoanReAmortizationRequest(); - PostLoansLoanIdTransactionsResponse response = ok(() -> fineractClient.loanTransactions().executeLoanTransaction1(loanExternalId, - reAmortizationRequest, Map.of("command", "reAmortize"))); + PostLoansLoanIdTransactionsResponse response = ok(() -> fineractClient.loanTransactions() + .executeLoanTransactionByLoanExternalId(loanExternalId, reAmortizationRequest, Map.of("command", "reAmortize"))); testContext().set(TestContextKey.LOAN_REAMORTIZATION_RESPONSE, response); } @@ -222,7 +222,7 @@ public void createReAmortizedPreviewByLoanExternalIdFailsWithErrorCode(int error final Map queryParams = Map.of("reAmortizationInterestHandling", reAmortizationInterestHandling); CallFailedRuntimeException exception = fail( - () -> fineractClient.loanTransactions().previewReAmortizationSchedule1(loanExternalId, queryParams)); + () -> fineractClient.loanTransactions().previewReAmortizationScheduleByLoanExternalId(loanExternalId, queryParams)); assertThat(exception.getStatus()).isEqualTo(errorCode); } @@ -271,7 +271,7 @@ private LoanScheduleData reAmortizedPreviewByLoanExternalId(final DataTable tabl final String reAmortizationInterestHandling = data.getFirst(); final Map queryParams = Map.of("reAmortizationInterestHandling", reAmortizationInterestHandling); - return ok(() -> fineractClient.loanTransactions().previewReAmortizationSchedule1(loanExternalId, queryParams)); + return ok(() -> fineractClient.loanTransactions().previewReAmortizationScheduleByLoanExternalId(loanExternalId, queryParams)); } @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java index 113f29ca8e3..fb73fd5402f 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java @@ -145,7 +145,7 @@ public void makeRepaymentWithGivenUser(String repaymentType, String transactionD PostUsersResponse createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE); Long createdUserId = createUserResponse.getResourceId(); - GetUsersUserIdResponse user = ok(() -> fineractClient.users().retrieveOne32(createdUserId)); + GetUsersUserIdResponse user = ok(() -> fineractClient.users().retrieveOneUser(createdUserId)); String apiBaseUrl = apiProperties.getBaseUrl() + "/fineract-provider/api/"; FineractFeignClient userClient = FineractFeignClient.builder().baseUrl(apiBaseUrl) @@ -174,8 +174,9 @@ public void makeRepaymentByExternalId(String repaymentType, String transactionDa String idempotencyKey = UUID.randomUUID().toString(); testContext().set(TestContextKey.TRANSACTION_IDEMPOTENCY_KEY, idempotencyKey); - PostLoansLoanIdTransactionsResponse repaymentResponse = ok(() -> fineractClient.loanTransactions() - .executeLoanTransaction1(resourceExternalId, repaymentRequest, Map.of("command", "repayment"))); + PostLoansLoanIdTransactionsResponse repaymentResponse = ok( + () -> fineractClient.loanTransactions().executeLoanTransactionByLoanExternalId(resourceExternalId, repaymentRequest, + Map.of("command", "repayment"))); testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse); eventCheckHelper.loanBalanceChangedEventCheck(loanId); @@ -200,15 +201,16 @@ public void makeRepaymentWithGivenUserByExternalId(String repaymentType, String PostUsersResponse createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE); Long createdUserId = createUserResponse.getResourceId(); - GetUsersUserIdResponse user = ok(() -> fineractClient.users().retrieveOne32(createdUserId)); + GetUsersUserIdResponse user = ok(() -> fineractClient.users().retrieveOneUser(createdUserId)); String apiBaseUrl = apiProperties.getBaseUrl() + "/fineract-provider/api/"; FineractFeignClient userClient = FineractFeignClient.builder().baseUrl(apiBaseUrl) .credentials(user.getUsername(), PWD_USER_WITH_ROLE).tenantId(apiProperties.getTenantId()).disableSslVerification(true) .readTimeout((int) apiProperties.getReadTimeout(), java.util.concurrent.TimeUnit.SECONDS).build(); - PostLoansLoanIdTransactionsResponse repaymentResponse = ok(() -> userClient.loanTransactions() - .executeLoanTransaction1(resourceExternalId, repaymentRequest, Map.of("command", "repayment"))); + PostLoansLoanIdTransactionsResponse repaymentResponse = ok( + () -> userClient.loanTransactions().executeLoanTransactionByLoanExternalId(resourceExternalId, repaymentRequest, + Map.of("command", "repayment"))); testContext().set(TestContextKey.LOAN_REPAYMENT_RESPONSE, repaymentResponse); eventCheckHelper.loanBalanceChangedEventCheck(loanId); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java index e20d0bf3c88..2f982a965e0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java @@ -42,6 +42,8 @@ import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest; import org.apache.fineract.test.data.LoanRescheduleErrorMessage; import org.apache.fineract.test.helper.ErrorMessageHelper; +import org.apache.fineract.test.messaging.event.EventCheckHelper; +import org.apache.fineract.test.messaging.store.EventStore; import org.apache.fineract.test.stepdef.AbstractStepDef; import org.apache.fineract.test.support.TestContextKey; import org.springframework.beans.factory.annotation.Autowired; @@ -56,9 +58,14 @@ public class LoanRescheduleStepDef extends AbstractStepDef { @Autowired private FineractFeignClient fineractClient; + @Autowired + private EventStore eventStore; + @Autowired + private EventCheckHelper eventCheckHelper; @When("Admin creates and approves Loan reschedule with the following data:") public void createAndApproveLoanReschedule(DataTable table) throws IOException { + eventStore.reset(); PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); long loanId = loanResponse.getLoanId(); @@ -100,6 +107,10 @@ public void createAndApproveLoanReschedule(DataTable table) throws IOException { ok(() -> fineractClient.rescheduleLoans().updateLoanRescheduleRequest(scheduleId, approveRequest, Map.of("command", "approve"))); + + if (newInterestRate != null) { + eventCheckHelper.loanBalanceChangedEventCheck(loanId); + } } @Then("Loan reschedule with the following data results a {int} error and {string} error message") diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 4e22724e987..0985f008a7d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -61,6 +61,7 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.avro.loan.v1.LoanAccountDataV1; import org.apache.fineract.avro.loan.v1.LoanChargePaidByDataV1; @@ -117,6 +118,7 @@ import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; import org.apache.fineract.client.models.PutLoansApprovedAmountRequest; import org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountRequest; +import org.apache.fineract.client.models.PutLoansLoanIdChargeData; import org.apache.fineract.client.models.PutLoansLoanIdRequest; import org.apache.fineract.client.models.PutLoansLoanIdResponse; import org.apache.fineract.test.data.AmortizationType; @@ -171,9 +173,9 @@ import org.apache.fineract.test.support.TestContextKey; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Assertions; -import org.springframework.beans.factory.annotation.Autowired; @Slf4j +@RequiredArgsConstructor public class LoanStepDef extends AbstractStepDef { public static final String DATE_FORMAT = "dd MMMM yyyy"; @@ -188,47 +190,24 @@ public class LoanStepDef extends AbstractStepDef { private static final DateTimeFormatter FORMATTER_EVENTS = DateTimeFormatter.ofPattern(DATE_FORMAT_EVENTS); private static final String TRANSACTION_DATE_FORMAT = "dd MMMM yyyy"; - @Autowired - private BusinessDateHelper businessDateHelper; - - @Autowired - private FineractFeignClient fineractClient; - - @Autowired - private EventAssertion eventAssertion; - - @Autowired - private PaymentTypeResolver paymentTypeResolver; - - @Autowired - private LoanProductResolver loanProductResolver; - - @Autowired - private LoanRequestFactory loanRequestFactory; - - @Autowired - private EventCheckHelper eventCheckHelper; + private final BusinessDateHelper businessDateHelper; + private final FineractFeignClient fineractClient; + private final EventAssertion eventAssertion; + private final PaymentTypeResolver paymentTypeResolver; + private final LoanProductResolver loanProductResolver; + private final LoanRequestFactory loanRequestFactory; + private final EventCheckHelper eventCheckHelper; + private final EventStore eventStore; + private final CodeValueResolver codeValueResolver; + private final CodeHelper codeHelper; + private final EventProperties eventProperties; + private final JobPollingProperties jobPollingProperties; private void storePaymentTransactionResponse(ApiResponse apiResponse) { testContext().set(TestContextKey.LOAN_PAYMENT_TRANSACTION_RESPONSE, apiResponse.getData()); testContext().set(TestContextKey.LOAN_PAYMENT_TRANSACTION_HEADERS, apiResponse.getHeaders()); } - @Autowired - private EventStore eventStore; - - @Autowired - private CodeValueResolver codeValueResolver; - - @Autowired - private CodeHelper codeHelper; - - @Autowired - private EventProperties eventProperties; - - @Autowired - private JobPollingProperties jobPollingProperties; - @When("Admin creates a new Loan") public void createLoan() { PostClientsResponse clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); @@ -1485,6 +1464,49 @@ public void modifyLoanSubmittedOnDate(String newSubmittedOnDate) { testContext().set(TestContextKey.LOAN_MODIFY_RESPONSE, responseMod); } + @Then("Admin modifies the loan and changes the ANNUAL interest rate to {string}") + public void modifyLoanInterestRate(final String newInterestRate) { + final PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final Long loanId = loanResponse.getResourceId(); + + final GetLoansLoanIdResponse loanDetails = ok( + () -> fineractClient.loans().retrieveLoan(loanId, Map.of("staffInSelectedOfficeOnly", "false", "associations", "charges"))); + + final PutLoansLoanIdRequest putLoansLoanIdRequest = new PutLoansLoanIdRequest()// + .productId(loanDetails.getLoanProductId())// + .principal(loanDetails.getPrincipal().longValue())// + .loanTermFrequency(loanDetails.getTermFrequency())// + .loanTermFrequencyType(loanDetails.getTermPeriodFrequencyType().getId())// + .numberOfRepayments(loanDetails.getNumberOfRepayments())// + .repaymentEvery(loanDetails.getRepaymentEvery())// + .repaymentFrequencyType(loanDetails.getRepaymentFrequencyType().getId())// + .interestRatePerPeriod(new BigDecimal(newInterestRate))// + .interestType(loanDetails.getInterestType().getId())// + .interestCalculationPeriodType(loanDetails.getInterestCalculationPeriodType().getId())// + .amortizationType(loanDetails.getAmortizationType().getId())// + .transactionProcessingStrategyCode(loanDetails.getTransactionProcessingStrategyCode())// + .expectedDisbursementDate(FORMATTER.format(loanDetails.getTimeline().getExpectedDisbursementDate()))// + .submittedOnDate(FORMATTER.format(loanDetails.getTimeline().getSubmittedOnDate()))// + .clientId(loanDetails.getClientId())// + .dateFormat(DATE_FORMAT)// + .locale("en")// + .loanType("individual");// + + final List existingCharges = loanDetails.getCharges(); + if (existingCharges != null && !existingCharges.isEmpty()) { + for (final GetLoansLoanIdLoanChargeData charge : existingCharges) { + putLoansLoanIdRequest.addChargesItem(new PutLoansLoanIdChargeData()// + .id(charge.getId())// + .chargeId(charge.getChargeId())// + .dueDate(charge.getDueDate().format(FORMATTER)).amount(charge.getAmountOrPercentage())); + } + } + + final PutLoansLoanIdResponse responseMod = ok( + () -> fineractClient.loans().modifyLoanApplication(loanId, putLoansLoanIdRequest, Map.of())); + testContext().set(TestContextKey.LOAN_MODIFY_RESPONSE, responseMod); + } + @Then("Admin fails to create a new customised Loan submitted on date: {string}, with Principal: {string}, a loanTermFrequency: {int} months, and numberOfRepayments: {int}") public void createCustomizedLoanFailure(String submitDate, String principal, Integer loanTermFrequency, Integer numberOfRepayments) { PostClientsResponse clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); @@ -1841,6 +1863,19 @@ public void disburseLoanFailureIsNotAllowed(String disbursementDate, String disb assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.disburseIsNotAllowedFailure()); } + @Then("Admin fails to disburse the loan on {string} with {string} amount due to exceed approved amount") + public void disburseIsNotAllowedExceedApprovedAmountFailure(String disbursementDate, String disbursementAmount) { + final PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.getLoanId(); + final PostLoansLoanIdRequest disburseRequest = LoanRequestFactory.defaultLoanDisburseRequest() + .actualDisbursementDate(disbursementDate).transactionAmount(new BigDecimal(disbursementAmount)); + + final CallFailedRuntimeException exception = fail( + () -> fineractClient.loans().stateTransitions(loanId, disburseRequest, Map.of("command", "disburse"))); + assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(403); + assertThat(exception.getDeveloperMessage()).contains(ErrorMessageHelper.disburseIsNotAllowedExceedApprovedAmountFailure()); + } + @Then("Admin fails to disburse the loan on {string} with {string} EUR transaction amount because of charge-off that was performed for the loan") public void disburseChargedOffLoanFailure(String actualDisbursementDate, String transactionAmount) { PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -2290,6 +2325,17 @@ public void loanTransactionsTabCheckNewAccruals(DataTable table) { .isEqualTo(expectedAccruals.size()); } + @Then("Loan has {double} total Accruals") + public void loanTransactionsTabCheckTotalAccruals(Double totalAccruedExpected) { + PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanCreateResponse.getLoanId(); + List transactions = getAccrualTransactions(loanId); + BigDecimal totalAccruedActual = transactions.stream().map(t -> isLoanTransactionAccrual(t) ? t.getAmount() : t.getAmount().negate()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + assertEquals(totalAccruedExpected, totalAccruedActual.doubleValue()); + } + @Then("Loan Transactions tab has no new accrual data") public void loanTransactionsTabCheckNoNewAccruals() { PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); @@ -2335,12 +2381,19 @@ public void loanTransactionsTabCheckWithoutAccruals(DataTable table) { checkLoanTransactionTab(data, transactions, header, resourceId); } + private boolean isLoanTransactionAccrual(GetLoansLoanIdTransactions lt) { + return "Accrual".equalsIgnoreCase(lt.getType().getValue()); + } + + private boolean isLoanTransactionAccrualAdjustment(GetLoansLoanIdTransactions lt) { + return "Accrual Adjustment".equalsIgnoreCase(lt.getType().getValue()); + } + public List getAccrualTransactions(Long loanId) { GetLoansLoanIdResponse loanDetailsResponse = ok(() -> fineractClient.loans().retrieveLoan(loanId, Map.of("staffInSelectedOfficeOnly", "false", "associations", "transactions"))); - return loanDetailsResponse.getTransactions().stream().filter( - lt -> "Accrual".equalsIgnoreCase(lt.getType().getValue()) || "Accrual Adjustment".equalsIgnoreCase(lt.getType().getValue())) - .toList(); + return loanDetailsResponse.getTransactions().stream() + .filter(lt -> isLoanTransactionAccrual(lt) || isLoanTransactionAccrualAdjustment(lt)).toList(); } public void checkLoanTransactionTabRows(List> data, List transactions, List header, @@ -3224,7 +3277,7 @@ public void deleteLoanWithExternalId() { PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); Long loanId = loanCreateResponse.getLoanId(); String loanExternalId = loanCreateResponse.getResourceExternalId(); - DeleteLoansLoanIdResponse deleteLoanResponse = ok(() -> fineractClient.loans().deleteLoanApplication1(loanExternalId)); + DeleteLoansLoanIdResponse deleteLoanResponse = ok(() -> fineractClient.loans().deleteLoanApplicationByExternalId(loanExternalId)); assertThat(deleteLoanResponse.getLoanId()).isEqualTo(loanId); assertThat(deleteLoanResponse.getResourceExternalId()).isEqualTo(loanExternalId); } @@ -3233,7 +3286,8 @@ public void deleteLoanWithExternalId() { public void failedDeleteLoanWithExternalId() { PostLoansResponse loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); String loanExternalId = loanCreateResponse.getResourceExternalId(); - CallFailedRuntimeException exception = fail(() -> fineractClient.loans().deleteLoanApplication1(loanExternalId.substring(5))); + CallFailedRuntimeException exception = fail( + () -> fineractClient.loans().deleteLoanApplicationByExternalId(loanExternalId.substring(5))); assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(404); } @@ -3465,7 +3519,7 @@ public void loanTransactionsRelationshipCheck(String nthTransactionFromStr, Stri @Then("Loan Product Charge-Off reasons options from loan product template have {int} options, with the following data:") public void loanProductTemplateChargeOffReasonOptionsCheck(final int linesExpected, final DataTable table) { final GetLoanProductsTemplateResponse loanProductDetails = ok( - () -> fineractClient.loanProducts().retrieveTemplate11(Map.of("staffInSelectedOfficeOnly", "false"))); + () -> fineractClient.loanProducts().retrieveTemplateLoanProduct(Map.of("staffInSelectedOfficeOnly", "false"))); assertNotNull(loanProductDetails); final List chargeOffReasonOptions = loanProductDetails.getChargeOffReasonOptions(); assertNotNull(chargeOffReasonOptions); @@ -5572,7 +5626,8 @@ public void updateLoanAvailableDisbursementAmountByExternalId(final String amoun final PutLoansAvailableDisbursementAmountRequest modifyLoanAvailableDisbursementAmountRequest = new PutLoansAvailableDisbursementAmountRequest() .locale(LOCALE_EN).amount(new BigDecimal(amount)); - ok(() -> fineractClient.loans().modifyLoanAvailableDisbursementAmount1(externalId, modifyLoanAvailableDisbursementAmountRequest)); + ok(() -> fineractClient.loans().modifyLoanAvailableDisbursementAmountByExternalId(externalId, + modifyLoanAvailableDisbursementAmountRequest)); } @Then("Update loan available disbursement amount is forbidden with amount {string} due to exceed applied amount") @@ -5582,8 +5637,8 @@ public void updateLoanAvailableDisbursementAmountForbiddenExceedAppliedAmount(fi final PutLoansAvailableDisbursementAmountRequest modifyLoanAvailableDisbursementAmountRequest = new PutLoansAvailableDisbursementAmountRequest() .locale(LOCALE_EN).amount(new BigDecimal(amount)); - final CallFailedRuntimeException exception = fail(() -> fineractClient.loans().modifyLoanAvailableDisbursementAmount1(externalId, - modifyLoanAvailableDisbursementAmountRequest)); + final CallFailedRuntimeException exception = fail(() -> fineractClient.loans() + .modifyLoanAvailableDisbursementAmountByExternalId(externalId, modifyLoanAvailableDisbursementAmountRequest)); assertThat(exception.getStatus()).isEqualTo(403); // API returns generic validation error - ideally should contain specific message about exceeding amount @@ -5752,7 +5807,7 @@ public void loanTransactionHasClassification(String transactionType, String expe private Long getClassificationCodeValueId(String codeName, String codeValueName) { // Check if code value already exists - List existingCodeValues = fineractClient.codeValues().retrieveAllCodeValues1(codeName); + List existingCodeValues = fineractClient.codeValues().retrieveAllCodeValuesByCodeName(codeName); // Try to find existing code value with the same name for (GetCodeValuesDataResponse codeValue : existingCodeValues) { if (codeValueName.equals(codeValue.getName())) { diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java new file mode 100644 index 00000000000..56e4caadd44 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalStepDef.java @@ -0,0 +1,808 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.loan; + +import static org.apache.fineract.client.feign.util.FeignCalls.fail; +import static org.apache.fineract.client.feign.util.FeignCalls.ok; +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.math.BigDecimal; +import java.util.Map; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.feign.services.WorkingCapitalLoanProductsApi; +import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.DeleteWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetConfigurableAttributes; +import org.apache.fineract.client.models.GetPaymentAllocation; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.PostAllowAttributeOverrides; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct; +import org.apache.fineract.test.factory.WorkingCapitalRequestFactory; +import org.apache.fineract.test.helper.ErrorMessageHelper; +import org.apache.fineract.test.helper.Utils; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContext; +import org.apache.fineract.test.support.TestContextKey; +import org.assertj.core.api.SoftAssertions; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +@RequiredArgsConstructor +public class WorkingCapitalStepDef extends AbstractStepDef { + + @Autowired + private WorkingCapitalRequestFactory workingCapitalRequestFactory; + + private final FineractFeignClient fineractFeignClient; + + public static final String NAME_FIELD_NAME = "name"; + public static final String SHORT_NAME_FIELD = "shortName"; + public static final String DESCRIPTION_FIELD_NAME = "description"; + public static final String CURRENCY_CODE_FIELD_NAME = "currencyCode"; + public static final String DIGITS_AFTER_DECIMAL_FIELD_NAME = "digitsAfterDecimal"; + public static final String IN_MULTIPLES_OF_FIELD_NAME = "inMultiplesOf"; + public static final String AMORTIZATION_TYPE_FIELD_NAME = "amortizationType"; + public static final String NPV_DAY_COUNT_FIELD_NAME = "npvDayCount"; + public static final String PRINCIPAL_FIELD_NAME = "principal"; + public static final String MIN_PRINCIPAL_FIELD_NAME = "minPrincipal"; + public static final String MAX_PRINCIPAL_FIELD_NAME = "maxPrincipal"; + public static final String PERIOD_PAYMENT_RATE_FIELD_NAME = "periodPaymentRate"; + public static final String MIN_PERIOD_PAYMENT_RATE_FIELD_NAME = "minPeriodPaymentRate"; + public static final String MAX_PERIOD_PAYMENT_RATE_FIELD_NAME = "maxPeriodPaymentRate"; + public static final String REPAYMENT_FREQUENCY_TYPE_FIELD_NAME = "repaymentFrequencyType"; + public static final String REPAYMENT_EVERY_FIELD_NAME = "repaymentEvery"; + public static final String EXTERNAL_ID_FIELD_NAME = "externalId"; + public static final String DELINQUENCY_BUCKET_ID_FIELD_NAME = "delinquencyBucketId"; + public static final String LOCALE_FIELD_NAME = "locale"; + + private WorkingCapitalLoanProductsApi workingCapitalApi() { + return fineractFeignClient.workingCapitalLoanProducts(); + } + + @When("Admin creates a new Working Capital Loan Product") + public void createWorkingCapitalLoanProduct() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName); // + final PostWorkingCapitalLoanProductsResponse responseDefaultWorkingCapitalLoanProductCreate = createWorkingCapitalLoanProduct( + defaultWorkingCapitalLoanProductCreateRequest); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE, responseDefaultWorkingCapitalLoanProductCreate); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST, defaultWorkingCapitalLoanProductCreateRequest); + checkWorkingCapitalLoanProductCreate(); + } + + @When("Admin creates a new Working Capital Loan Product with external-id") + public void createWorkingCapitalLoanProductWithExternalId() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName) // + .externalId("EXT-WCP-" + UUID.randomUUID());// + final PostWorkingCapitalLoanProductsResponse responseDefaultWorkingCapitalLoanProductCreate = createWorkingCapitalLoanProduct( + defaultWorkingCapitalLoanProductCreateRequest); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE, responseDefaultWorkingCapitalLoanProductCreate); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST, defaultWorkingCapitalLoanProductCreateRequest); + checkWorkingCapitalLoanProductWithExternalIdCreate(); + } + + @Then("Admin failed to create a new Working Capital Loan Product field {string} with empty or null mandatory data {string}") + public void createWorkingCapitalLoanProductWithEmptyDataFailed(String fieldName, String value) { + String errorMessage = ErrorMessageHelper.fieldValueNullOrEmptyMandatoryFailure(fieldName); + createWorkingCapitalLoanProductWithInvalidDataFailure(fieldName, value, errorMessage); + } + + @Then("Admin failed to create a new Working Capital Loan Product field {string} with max length data {int} while max allowed is {int}") + public void createWorkingCapitalLoanProductWithMaxLengthDataFailed(String fieldName, int maxLengthValue, int maxAllowedLengthValue) { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName); // + String value = Utils.randomStringGenerator(maxLengthValue); + final PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequestUpdated = setWorkingCapitalLoanProductsCreateFieldValue( + defaultWorkingCapitalLoanProductCreateRequest, fieldName, value); + + String errorMessage = ErrorMessageHelper.fieldValueMoreMaxLengthAllowedFailure(fieldName, maxAllowedLengthValue); + checkCreateWorkingCapitalLoanProductWithInvalidDataFailure(workingCapitalLoanProductCreateRequestUpdated, errorMessage); + } + + @Then("Admin failed to create a new Working Capital Loan Product field {string} with zero incorrect value") + public void createWorkingCapitalLoanProductWithZeroValueDataFailed(String fieldName) { + String errorMessage = ErrorMessageHelper.fieldValueZeroValueFailure(fieldName); + createWorkingCapitalLoanProductWithInvalidDataFailure(fieldName, "0", errorMessage); + } + + @Then("Admin failed to create a new Working Capital Loan Product field {string} with invalid data {string} and got an error {string}") + public void createWorkingCapitalLoanProductWithInvalidDataFailed(String fieldName, String value, String errorMessage) { + createWorkingCapitalLoanProductWithInvalidDataFailure(fieldName, value, errorMessage); + } + + @Then("Admin failed to create a new Working Capital Loan Product with invalid number of payment allocation rules") + public void createWorkingCapitalLoanProductWithInvalidNumberPaymentAllocationFailed() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName) // + .paymentAllocation( + workingCapitalRequestFactory.invalidNumberOfPaymentAllocationRulesForWorkingCapitalLoanProductCreateRequest()); + + String errorMessage = ErrorMessageHelper.paymentAllocationRulesInvalidNumberFailure(4); + checkCreateWorkingCapitalLoanProductWithInvalidDataFailure(defaultWorkingCapitalLoanProductCreateRequest, errorMessage); + } + + @Then("Admin failed to create a new Working Capital Loan Product with invalid value of payment allocation rules") + public void createWorkingCapitalLoanProductWithInvalidPaymentAllocationFailed() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName) // + .paymentAllocation(workingCapitalRequestFactory.invalidPaymentAllocationRulesForWorkingCapitalLoanProductCreateRequest()); + + String errorMessage = ErrorMessageHelper.paymentAllocationRulesInvalidValueFailure(); + checkCreateWorkingCapitalLoanProductWithInvalidDataFailure(defaultWorkingCapitalLoanProductCreateRequest, errorMessage); + } + + @When("Admin updates a Working Capital Loan Product") + public void updateWorkingCapitalLoanProduct() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final String workingCapitalProductDefaultShortName = Utils.randomStringGenerator(4); + final PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductUpdateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequestUpdate() // + .name(workingCapitalProductDefaultName) // + .shortName(workingCapitalProductDefaultShortName)// + .externalId("EXT-WCP-" + UUID.randomUUID()); + + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + PutWorkingCapitalLoanProductsProductIdResponse responseWorkingCapitalLoanProductUpdate = ok( + () -> workingCapitalApi().updateWorkingCapitalLoanProduct(resourceId, workingCapitalLoanProductUpdateRequest, Map.of())); + + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_RESPONSE, responseWorkingCapitalLoanProductUpdate); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST, workingCapitalLoanProductUpdateRequest); + checkWorkingCapitalLoanProductUpdate(); + } + + @When("Admin updates a Working Capital Loan Product via external-id") + public void updateWorkingCapitalLoanProductViaExternalId() { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final String workingCapitalProductDefaultShortName = Utils.randomStringGenerator(4); + final PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductUpdateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequestUpdate() // + .name(workingCapitalProductDefaultName) // + .shortName(workingCapitalProductDefaultShortName)// + .externalId("EXT-WCP-" + UUID.randomUUID()); + + PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductsRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST); + String externalId = workingCapitalLoanProductsRequest.getExternalId(); + + PutWorkingCapitalLoanProductsProductIdResponse responseWorkingCapitalLoanProductUpdate = ok( + () -> workingCapitalApi().updateWorkingCapitalLoanProductByExternalId(externalId, workingCapitalLoanProductUpdateRequest, Map.of())); + + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_RESPONSE, responseWorkingCapitalLoanProductUpdate); + testContext().set(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST, workingCapitalLoanProductUpdateRequest); + checkWorkingCapitalLoanProductWithExternalIdUpdate(); + } + + @Then("Admin failed to update a new Working Capital Loan Product field {string} with max length data {int} while max allowed is {int}") + public void updateWorkingCapitalLoanProductWithMaxLengthDataFailed(String fieldName, int maxLengthValue, int maxAllowedLengthValue) { + String value = Utils.randomStringGenerator(maxLengthValue); + PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest = new PutWorkingCapitalLoanProductsProductIdRequest(); + final PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductUpdateRequestUpdated = setWorkingCapitalLoanProductsUpdateRequest( + defaultWorkingCapitalLoanProductUpdateRequest, fieldName, value); + + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + String errorMessage = ErrorMessageHelper.fieldValueMoreMaxLengthAllowedFailure(fieldName, maxAllowedLengthValue); + checkUpdateWorkingCapitalLoanProductWithInvalidDataFailure(resourceId, workingCapitalLoanProductUpdateRequestUpdated, errorMessage); + } + + @Then("Admin failed to update a new Working Capital Loan Product field {string} with zero incorrect value") + public void updateWorkingCapitalLoanProductWithZeroValueDataFailed(String fieldName) { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + String errorMessage = ErrorMessageHelper.fieldValueZeroValueFailure(fieldName); + updateWorkingCapitalLoanProductWithInvalidDataFailure(resourceId, fieldName, "0", errorMessage); + } + + @Then("Admin failed to update a new Working Capital Loan Product field {string} with invalid data {string} and got an error {string}") + public void updateWorkingCapitalLoanProductWithInvalidDataFailed(String fieldName, String value, String errorMessage) { + final PostWorkingCapitalLoanProductsRequest workingCapitalProductForUpdateRequest = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST_FOR_UPDATE_WCLP); + String workingCapitalProductName = workingCapitalProductForUpdateRequest.getName(); + final PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequestUpdate() // + .name(workingCapitalProductName); // + + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + updateWorkingCapitalLoanProductWithInvalidDataFailure(defaultWorkingCapitalLoanProductUpdateRequest, resourceId, fieldName, value, + errorMessage); + } + + @Then("Admin failed to update a new Working Capital Loan Product with invalid number of payment allocation rules") + public void updateWorkingCapitalLoanProductWithInvalidNumberPaymentAllocationFailed() { + final PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest = new PutWorkingCapitalLoanProductsProductIdRequest() + .paymentAllocation( + workingCapitalRequestFactory.invalidNumberOfPaymentAllocationRulesForWorkingCapitalLoanProductUpdateRequest()); + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + String errorMessage = ErrorMessageHelper.paymentAllocationRulesInvalidNumberFailure(4); + checkUpdateWorkingCapitalLoanProductWithInvalidDataFailure(resourceId, defaultWorkingCapitalLoanProductUpdateRequest, errorMessage); + } + + @Then("Admin failed to update a new Working Capital Loan Product with invalid value of payment allocation rules") + public void updateWorkingCapitalLoanProductWithInvalidPaymentAllocationFailed() { + final PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest = new PutWorkingCapitalLoanProductsProductIdRequest() + .paymentAllocation(workingCapitalRequestFactory.invalidPaymentAllocationRulesForWorkingCapitalLoanProductUpdateRequest()); + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = TestContext.GLOBAL + .get(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + String errorMessage = ErrorMessageHelper.paymentAllocationRulesInvalidValueFailure(); + checkUpdateWorkingCapitalLoanProductWithInvalidDataFailure(resourceId, defaultWorkingCapitalLoanProductUpdateRequest, errorMessage); + } + + @Then("Admin failed to retrieve a Working Capital Loan Product with id {int} that doesn't exist") + public void retrieveWorkingCapitalLoanProductFailure(Integer productId) { + CallFailedRuntimeException exception = fail( + () -> workingCapitalApi().retrieveOneWorkingCapitalLoanProduct(Long.valueOf(productId), Map.of())); + assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(404); + assertThat(exception.getDeveloperMessage()) + .contains(ErrorMessageHelper.workingCapitalLoanProductIdentifiedDoesNotExistFailure(String.valueOf(productId))); + } + + @Then("Admin deletes a Working Capital Loan Product") + public void deleteWorkingCapitalLoanProduct() { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + DeleteWorkingCapitalLoanProductsProductIdResponse deleteWorkingCapitalLoanProductResponse = ok( + () -> workingCapitalApi().deleteWorkingCapitalLoanProduct(resourceId, Map.of())); + assertThat(deleteWorkingCapitalLoanProductResponse.getResourceId()).isEqualTo(resourceId); + } + + @Then("Admin deletes a Working Capital Loan Product via external-id") + public void deleteWorkingCapitalLoanProductViaExternalId() { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + + PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductsUpdateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST); + String externalId = workingCapitalLoanProductsUpdateRequest.getExternalId(); + + DeleteWorkingCapitalLoanProductsProductIdResponse deleteWorkingCapitalLoanProductResponse = ok( + () -> workingCapitalApi().deleteWorkingCapitalLoanProductByExternalId(externalId, Map.of())); + assertThat(deleteWorkingCapitalLoanProductResponse.getResourceId()).isEqualTo(resourceId); + } + + @Then("Admin checks a Working Capital Loan Product is deleted and doesn't exist") + public void checkWorkingCapitalLoanProductIsDeleted() { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductsResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductsResponse.getResourceId(); + checkWorkingCapitalLoanProductDeleteFailure(resourceId); + } + + @Then("Admin failed to delete a Working Capital Loan Product with id {int} that doesn't exist") + public void checkWorkingCapitalLoanProductIsDeleted(Integer productId) { + checkWorkingCapitalLoanProductDeleteFailure(Long.valueOf(productId)); + } + + @Then("Admin checks a Working Capital Loan Product is deleted and doesn't exist via external-id") + public void checkWorkingCapitalLoanProductIsDeletedViaExternalId() { + PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductsUpdateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST); + String externalId = workingCapitalLoanProductsUpdateRequest.getExternalId(); + + CallFailedRuntimeException exception = fail( + () -> workingCapitalApi().retrieveOneWorkingCapitalLoanProductByExternalId(externalId, Map.of())); + assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(404); + assertThat(exception.getDeveloperMessage()) + .contains(ErrorMessageHelper.workingCapitalLoanProductIdentifiedDoesNotExistFailure(String.valueOf(externalId))); + } + + public PostWorkingCapitalLoanProductsResponse createWorkingCapitalLoanProduct( + PostWorkingCapitalLoanProductsRequest workingCapitalProductRequest) { + String workingCapitalProductName = workingCapitalProductRequest.getName(); + log.debug("Creating new working capital product: {}", workingCapitalProductName); + try { + PostWorkingCapitalLoanProductsResponse response = ok( + () -> workingCapitalApi().createWorkingCapitalLoanProduct(workingCapitalProductRequest, Map.of())); + log.debug("Successfully created working capital product '{}' with ID: {}", workingCapitalProductName, response.getResourceId()); + return response; + } catch (Exception e) { + log.error("FAILED to create working capital product '{}'", workingCapitalProductName, e); + throw e; + } + } + + public void checkWorkingCapitalLoanProductCreate() { + PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST); + + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductResponse.getResourceId(); + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse = workingCapitalApi() + .retrieveOneWorkingCapitalLoanProduct(resourceId, Map.of()); + checkWorkingCapitalLoanProductCreate(workingCapitalLoanProductCreateRequest, getWorkingCapitalProductResponse); + } + + public void checkWorkingCapitalLoanProductWithExternalIdCreate() { + PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST); + String externalId = workingCapitalLoanProductCreateRequest.getExternalId(); + + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse = workingCapitalApi() + .retrieveOneWorkingCapitalLoanProductByExternalId(externalId, Map.of()); + checkWorkingCapitalLoanProductCreate(workingCapitalLoanProductCreateRequest, getWorkingCapitalProductResponse); + } + + public void checkWorkingCapitalLoanProductCreate(PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequest, + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse) { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductResponse.getResourceId(); + + SoftAssertions assertions = new SoftAssertions(); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getName()).isEqualTo(getWorkingCapitalProductResponse.getName()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getShortName()) + .isEqualTo(getWorkingCapitalProductResponse.getShortName()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getDescription()) + .isEqualTo(getWorkingCapitalProductResponse.getDescription()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getExternalId()) + .isEqualTo(getWorkingCapitalProductResponse.getExternalId()); + assertions.assertThat(resourceId).isEqualTo(getWorkingCapitalProductResponse.getId()); + + // check currency + assert getWorkingCapitalProductResponse.getCurrency() != null; + assertions.assertThat(workingCapitalLoanProductCreateRequest.getCurrencyCode()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getCode()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getDigitsAfterDecimal()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getDecimalPlaces()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getInMultiplesOf()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getInMultiplesOf()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getStartDate()) + .isEqualTo(getWorkingCapitalProductResponse.getStartDate()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getCloseDate()) + .isEqualTo(getWorkingCapitalProductResponse.getCloseDate()); + + assert getWorkingCapitalProductResponse.getAmortizationType() != null; + assert workingCapitalLoanProductCreateRequest.getAmortizationType() != null; + assertions.assertThat(workingCapitalLoanProductCreateRequest.getAmortizationType().getValue()) + .isEqualTo(getWorkingCapitalProductResponse.getAmortizationType().getCode()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getNpvDayCount()) + .isEqualTo(getWorkingCapitalProductResponse.getNpvDayCount()); + assertions.assertThat( + workingCapitalLoanProductCreateRequest.getRepaymentEvery().compareTo(getWorkingCapitalProductResponse.getRepaymentEvery())) + .isEqualTo(0); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getRepaymentFrequencyType().getValue()) + .isEqualTo(getWorkingCapitalProductResponse.getRepaymentFrequencyType().getCode()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getPeriodPaymentRate() + .compareTo(getWorkingCapitalProductResponse.getPeriodPaymentRate())).isEqualTo(0); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getMinPeriodPaymentRate()) + .isEqualTo(getWorkingCapitalProductResponse.getMinPeriodPaymentRate()); + assertions.assertThat(workingCapitalLoanProductCreateRequest.getMaxPeriodPaymentRate()) + .isEqualTo(getWorkingCapitalProductResponse.getMaxPeriodPaymentRate()); + + // check payment allocation rules + assert workingCapitalLoanProductCreateRequest.getPaymentAllocation() != null; + workingCapitalLoanProductCreateRequest.getPaymentAllocation().forEach(paymentAllocation -> { + assert getWorkingCapitalProductResponse.getPaymentAllocation() != null; + GetPaymentAllocation getPaymentAllocation = getWorkingCapitalProductResponse.getPaymentAllocation().stream() + .filter(paymentAllocationSearched -> { + assert paymentAllocation.getTransactionType() != null; + return paymentAllocation.getTransactionType().getValue().equals(paymentAllocationSearched.getTransactionType()); + }).findFirst().orElseThrow(() -> new RuntimeException("No paymentAllocation is found!")); + assertions.assertThat(paymentAllocation.getPaymentAllocationOrder()) + .containsAll(getPaymentAllocation.getPaymentAllocationOrder()); + }); + + assertions + .assertThat( + workingCapitalLoanProductCreateRequest.getPrincipal().compareTo(getWorkingCapitalProductResponse.getPrincipal())) + .isEqualTo(0); + assertions.assertThat( + workingCapitalLoanProductCreateRequest.getMaxPrincipal().compareTo(getWorkingCapitalProductResponse.getMaxPrincipal())) + .isEqualTo(0); + assertions.assertThat( + workingCapitalLoanProductCreateRequest.getMinPrincipal().compareTo(getWorkingCapitalProductResponse.getMinPrincipal())) + .isEqualTo(0); + + if (workingCapitalLoanProductCreateRequest.getAllowAttributeOverrides() != null) { + PostAllowAttributeOverrides allowAttributeOverridesCreateResponse = workingCapitalLoanProductCreateRequest + .getAllowAttributeOverrides(); + GetConfigurableAttributes allowAttributeOverridesGetResponse = getWorkingCapitalProductResponse.getAllowAttributeOverrides(); + assert allowAttributeOverridesGetResponse != null; + assertions.assertThat(allowAttributeOverridesCreateResponse.getDiscountDefault()) + .isEqualTo(allowAttributeOverridesGetResponse.getDiscountDefault()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getFlatPercentageAmount()) + .isEqualTo(allowAttributeOverridesGetResponse.getFlatPercentageAmount()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getDelinquencyBucketClassification()) + .isEqualTo(allowAttributeOverridesGetResponse.getDelinquencyBucketClassification()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getPeriodPaymentFrequency()) + .isEqualTo(allowAttributeOverridesGetResponse.getPeriodPaymentFrequency()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getPeriodPaymentFrequencyType()) + .isEqualTo(allowAttributeOverridesGetResponse.getPeriodPaymentFrequencyType()); + } + assertions.assertAll(); + } + + public void checkWorkingCapitalLoanProductUpdate() { + PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductsUpdateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST); + + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductResponse.getResourceId(); + + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse = workingCapitalApi() + .retrieveOneWorkingCapitalLoanProduct(resourceId, Map.of()); + checkWorkingCapitalLoanProductUpdate(workingCapitalLoanProductsUpdateRequest, getWorkingCapitalProductResponse); + } + + public void checkWorkingCapitalLoanProductWithExternalIdUpdate() { + PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductsUpdateRequest = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST); + String externalId = workingCapitalLoanProductsUpdateRequest.getExternalId(); + + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse = workingCapitalApi() + .retrieveOneWorkingCapitalLoanProductByExternalId(externalId, Map.of()); + checkWorkingCapitalLoanProductUpdate(workingCapitalLoanProductsUpdateRequest, getWorkingCapitalProductResponse); + } + + public void checkWorkingCapitalLoanProductUpdate(PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductsUpdateRequest, + GetWorkingCapitalLoanProductsProductIdResponse getWorkingCapitalProductResponse) { + PostWorkingCapitalLoanProductsResponse workingCapitalLoanProductResponse = testContext() + .get(TestContextKey.WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE); + Long resourceId = workingCapitalLoanProductResponse.getResourceId(); + + SoftAssertions assertions = new SoftAssertions(); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getName()).isEqualTo(getWorkingCapitalProductResponse.getName()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getShortName()) + .isEqualTo(getWorkingCapitalProductResponse.getShortName()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getDescription()) + .isEqualTo(getWorkingCapitalProductResponse.getDescription()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getExternalId()) + .isEqualTo(getWorkingCapitalProductResponse.getExternalId()); + assertions.assertThat(resourceId).isEqualTo(getWorkingCapitalProductResponse.getId()); + + // check currency + assert getWorkingCapitalProductResponse.getCurrency() != null; + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getCurrencyCode()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getCode()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getDigitsAfterDecimal()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getDecimalPlaces()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getInMultiplesOf()) + .isEqualTo(getWorkingCapitalProductResponse.getCurrency().getInMultiplesOf()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getStartDate()) + .isEqualTo(getWorkingCapitalProductResponse.getStartDate()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getCloseDate()) + .isEqualTo(getWorkingCapitalProductResponse.getCloseDate()); + + assert getWorkingCapitalProductResponse.getAmortizationType() != null; + assert workingCapitalLoanProductsUpdateRequest.getAmortizationType() != null; + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getAmortizationType().getValue()) + .isEqualTo(getWorkingCapitalProductResponse.getAmortizationType().getCode()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getNpvDayCount()) + .isEqualTo(getWorkingCapitalProductResponse.getNpvDayCount()); + assertions.assertThat( + workingCapitalLoanProductsUpdateRequest.getRepaymentEvery().compareTo(getWorkingCapitalProductResponse.getRepaymentEvery())) + .isEqualTo(0); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getRepaymentFrequencyType().getValue()) + .isEqualTo(getWorkingCapitalProductResponse.getRepaymentFrequencyType().getCode()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getPeriodPaymentRate() + .compareTo(getWorkingCapitalProductResponse.getPeriodPaymentRate())).isEqualTo(0); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getMinPeriodPaymentRate()) + .isEqualTo(getWorkingCapitalProductResponse.getMinPeriodPaymentRate()); + assertions.assertThat(workingCapitalLoanProductsUpdateRequest.getMaxPeriodPaymentRate()) + .isEqualTo(getWorkingCapitalProductResponse.getMaxPeriodPaymentRate()); + + // check payment allocation rules + assert workingCapitalLoanProductsUpdateRequest.getPaymentAllocation() != null; + workingCapitalLoanProductsUpdateRequest.getPaymentAllocation().forEach(paymentAllocation -> { + assert getWorkingCapitalProductResponse.getPaymentAllocation() != null; + GetPaymentAllocation getPaymentAllocation = getWorkingCapitalProductResponse.getPaymentAllocation().stream() + .filter(paymentAllocationSearched -> { + assert paymentAllocation.getTransactionType() != null; + return paymentAllocation.getTransactionType().getValue().equals(paymentAllocationSearched.getTransactionType()); + }).findFirst().orElseThrow(() -> new RuntimeException("No paymentAllocation is found!")); + assertions.assertThat(paymentAllocation.getPaymentAllocationOrder()) + .containsAll(getPaymentAllocation.getPaymentAllocationOrder()); + }); + + assertions + .assertThat( + workingCapitalLoanProductsUpdateRequest.getPrincipal().compareTo(getWorkingCapitalProductResponse.getPrincipal())) + .isEqualTo(0); + assertions.assertThat( + workingCapitalLoanProductsUpdateRequest.getMaxPrincipal().compareTo(getWorkingCapitalProductResponse.getMaxPrincipal())) + .isEqualTo(0); + assertions.assertThat( + workingCapitalLoanProductsUpdateRequest.getMinPrincipal().compareTo(getWorkingCapitalProductResponse.getMinPrincipal())) + .isEqualTo(0); + + if (workingCapitalLoanProductsUpdateRequest.getAllowAttributeOverrides() != null) { + PostAllowAttributeOverrides allowAttributeOverridesCreateResponse = workingCapitalLoanProductsUpdateRequest + .getAllowAttributeOverrides(); + GetConfigurableAttributes allowAttributeOverridesGetResponse = getWorkingCapitalProductResponse.getAllowAttributeOverrides(); + assert allowAttributeOverridesGetResponse != null; + assertions.assertThat(allowAttributeOverridesCreateResponse.getDiscountDefault()) + .isEqualTo(allowAttributeOverridesGetResponse.getDiscountDefault()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getFlatPercentageAmount()) + .isEqualTo(allowAttributeOverridesGetResponse.getFlatPercentageAmount()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getDelinquencyBucketClassification()) + .isEqualTo(allowAttributeOverridesGetResponse.getDelinquencyBucketClassification()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getPeriodPaymentFrequency()) + .isEqualTo(allowAttributeOverridesGetResponse.getPeriodPaymentFrequency()); + assertions.assertThat(allowAttributeOverridesCreateResponse.getPeriodPaymentFrequencyType()) + .isEqualTo(allowAttributeOverridesGetResponse.getPeriodPaymentFrequencyType()); + } + assertions.assertAll(); + } + + public void createWorkingCapitalLoanProductWithInvalidDataFailure(String fieldName, String value, String errorMessage) { + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName() + + Utils.randomStringGenerator("_", 10); + final PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName); // + + final PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequestUpdated = setWorkingCapitalLoanProductsCreateFieldValue( + defaultWorkingCapitalLoanProductCreateRequest, fieldName, value); + checkCreateWorkingCapitalLoanProductWithInvalidDataFailure(workingCapitalLoanProductCreateRequestUpdated, errorMessage); + } + + public void checkCreateWorkingCapitalLoanProductWithInvalidDataFailure( + PostWorkingCapitalLoanProductsRequest workingCapitalLoanProductCreateRequestUpdated, String errorMessage) { + CallFailedRuntimeException exception = fail( + () -> workingCapitalApi().createWorkingCapitalLoanProduct(workingCapitalLoanProductCreateRequestUpdated, Map.of())); + assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(400); + assertThat(exception.getDeveloperMessage()).contains(errorMessage); + } + + public PostWorkingCapitalLoanProductsRequest setWorkingCapitalLoanProductsCreateFieldValue( + PostWorkingCapitalLoanProductsRequest defaultWorkingCapitalLoanProductCreateRequest, String fieldName, String fieldValue) { + if (fieldValue.equals("null")) { + fieldValue = null; + } + Integer valueInteger = null; + BigDecimal valueBigDecimal = null; + if (fieldName.equalsIgnoreCase(DIGITS_AFTER_DECIMAL_FIELD_NAME) || fieldName.equalsIgnoreCase(IN_MULTIPLES_OF_FIELD_NAME) + || fieldName.equalsIgnoreCase(NPV_DAY_COUNT_FIELD_NAME) || fieldName.equalsIgnoreCase(REPAYMENT_EVERY_FIELD_NAME)) { + valueInteger = fieldValue != null ? Integer.valueOf(fieldValue) : null; + } + if (fieldName.equalsIgnoreCase(PRINCIPAL_FIELD_NAME) || fieldName.equalsIgnoreCase(MIN_PRINCIPAL_FIELD_NAME) + || fieldName.equalsIgnoreCase(MAX_PRINCIPAL_FIELD_NAME) || fieldName.equalsIgnoreCase(PERIOD_PAYMENT_RATE_FIELD_NAME) + || fieldName.equalsIgnoreCase(MIN_PERIOD_PAYMENT_RATE_FIELD_NAME) + || fieldName.equalsIgnoreCase(MAX_PERIOD_PAYMENT_RATE_FIELD_NAME)) { + valueBigDecimal = fieldValue != null ? new BigDecimal(fieldValue) : null; + } + + switch (fieldName) { + case NAME_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setName(fieldValue); + break; + case SHORT_NAME_FIELD: + defaultWorkingCapitalLoanProductCreateRequest.setShortName(fieldValue); + break; + case DESCRIPTION_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setDescription(fieldValue); + break; + case CURRENCY_CODE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setCurrencyCode(fieldValue); + break; + case DIGITS_AFTER_DECIMAL_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setDigitsAfterDecimal(valueInteger); + break; + case IN_MULTIPLES_OF_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setInMultiplesOf(valueInteger); + break; + case AMORTIZATION_TYPE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setAmortizationType( + fieldValue == null ? null : PostWorkingCapitalLoanProductsRequest.AmortizationTypeEnum.valueOf(fieldValue)); + break; + case NPV_DAY_COUNT_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setNpvDayCount(valueInteger); + break; + case PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setPrincipal(valueBigDecimal); + break; + case MIN_PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setMinPrincipal(valueBigDecimal); + break; + case MAX_PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setMaxPrincipal(valueBigDecimal); + break; + case PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setPeriodPaymentRate(valueBigDecimal); + break; + case MIN_PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setMinPeriodPaymentRate(valueBigDecimal); + break; + case MAX_PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setMaxPeriodPaymentRate(valueBigDecimal); + break; + case REPAYMENT_FREQUENCY_TYPE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setRepaymentFrequencyType( + fieldValue == null ? null : PostWorkingCapitalLoanProductsRequest.RepaymentFrequencyTypeEnum.valueOf(fieldValue)); + break; + case REPAYMENT_EVERY_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setRepaymentEvery(valueInteger); + break; + case EXTERNAL_ID_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setExternalId(fieldValue); + break; + case DELINQUENCY_BUCKET_ID_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest + .setDelinquencyBucketId(fieldValue != null ? Long.parseLong(fieldValue) : null); + break; + case LOCALE_FIELD_NAME: + defaultWorkingCapitalLoanProductCreateRequest.setLocale(fieldValue); + break; + default: + break; + } + return defaultWorkingCapitalLoanProductCreateRequest; + } + + public void updateWorkingCapitalLoanProductWithInvalidDataFailure(Long productId, String fieldName, String value, String errorMessage) { + final PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest = new PutWorkingCapitalLoanProductsProductIdRequest(); + updateWorkingCapitalLoanProductWithInvalidDataFailure(defaultWorkingCapitalLoanProductUpdateRequest, productId, fieldName, value, + errorMessage); + } + + public void updateWorkingCapitalLoanProductWithInvalidDataFailure( + PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest, Long productId, String fieldName, + String value, String errorMessage) { + final PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductUpdateRequestUpdated = setWorkingCapitalLoanProductsUpdateRequest( + defaultWorkingCapitalLoanProductUpdateRequest, fieldName, value); + checkUpdateWorkingCapitalLoanProductWithInvalidDataFailure(productId, workingCapitalLoanProductUpdateRequestUpdated, errorMessage); + } + + public void checkUpdateWorkingCapitalLoanProductWithInvalidDataFailure(Long productId, + PutWorkingCapitalLoanProductsProductIdRequest workingCapitalLoanProductUpdateRequestUpdated, String errorMessage) { + CallFailedRuntimeException exception = fail(() -> workingCapitalApi().updateWorkingCapitalLoanProduct(productId, + workingCapitalLoanProductUpdateRequestUpdated, Map.of())); + assertThat(exception.getStatus()).as(ErrorMessageHelper.incorrectExpectedValueInResponse()).isEqualTo(400); + assertThat(exception.getDeveloperMessage()).contains(errorMessage); + } + + public PutWorkingCapitalLoanProductsProductIdRequest setWorkingCapitalLoanProductsUpdateRequest( + PutWorkingCapitalLoanProductsProductIdRequest defaultWorkingCapitalLoanProductUpdateRequest, String fieldName, + String fieldValue) { + if (fieldValue.equals("null")) { + fieldValue = null; + } + + Integer valueInteger = null; + BigDecimal valueBigDecimal = null; + if (fieldName.equalsIgnoreCase(DIGITS_AFTER_DECIMAL_FIELD_NAME) || fieldName.equalsIgnoreCase(IN_MULTIPLES_OF_FIELD_NAME) + || fieldName.equalsIgnoreCase(NPV_DAY_COUNT_FIELD_NAME) || fieldName.equalsIgnoreCase(REPAYMENT_EVERY_FIELD_NAME)) { + valueInteger = fieldValue != null ? Integer.valueOf(fieldValue) : null; + } + if (fieldName.equalsIgnoreCase(PRINCIPAL_FIELD_NAME) || fieldName.equalsIgnoreCase(MIN_PRINCIPAL_FIELD_NAME) + || fieldName.equalsIgnoreCase(MAX_PRINCIPAL_FIELD_NAME) || fieldName.equalsIgnoreCase(PERIOD_PAYMENT_RATE_FIELD_NAME) + || fieldName.equalsIgnoreCase(MIN_PERIOD_PAYMENT_RATE_FIELD_NAME) + || fieldName.equalsIgnoreCase(MAX_PERIOD_PAYMENT_RATE_FIELD_NAME)) { + valueBigDecimal = fieldValue != null ? new BigDecimal(fieldValue) : null; + } + + switch (fieldName) { + case NAME_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setName(fieldValue); + break; + case SHORT_NAME_FIELD: + defaultWorkingCapitalLoanProductUpdateRequest.setShortName(fieldValue); + break; + case DESCRIPTION_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setDescription(fieldValue); + break; + case CURRENCY_CODE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setCurrencyCode(fieldValue); + break; + case DIGITS_AFTER_DECIMAL_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setDigitsAfterDecimal(valueInteger); + break; + case IN_MULTIPLES_OF_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setInMultiplesOf(valueInteger); + break; + case AMORTIZATION_TYPE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setAmortizationType( + fieldValue == null ? null : PutWorkingCapitalLoanProductsProductIdRequest.AmortizationTypeEnum.valueOf(fieldValue)); + break; + case NPV_DAY_COUNT_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setNpvDayCount(valueInteger); + break; + case PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setPrincipal(valueBigDecimal); + break; + case MIN_PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setMinPrincipal(valueBigDecimal); + break; + case MAX_PRINCIPAL_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setMaxPrincipal(valueBigDecimal); + break; + case PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setPeriodPaymentRate(valueBigDecimal); + break; + case MIN_PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setMinPeriodPaymentRate(valueBigDecimal); + break; + case MAX_PERIOD_PAYMENT_RATE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setMaxPeriodPaymentRate(valueBigDecimal); + break; + case REPAYMENT_FREQUENCY_TYPE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setRepaymentFrequencyType(fieldValue == null ? null + : PutWorkingCapitalLoanProductsProductIdRequest.RepaymentFrequencyTypeEnum.valueOf(fieldValue)); + break; + case REPAYMENT_EVERY_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setRepaymentEvery(valueInteger); + break; + case EXTERNAL_ID_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setExternalId(fieldValue); + break; + case DELINQUENCY_BUCKET_ID_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest + .setDelinquencyBucketId(fieldValue != null ? Long.parseLong(fieldValue) : null); + break; + case LOCALE_FIELD_NAME: + defaultWorkingCapitalLoanProductUpdateRequest.setLocale(fieldValue); + break; + default: + break; + } + return defaultWorkingCapitalLoanProductUpdateRequest; + } + + public void checkWorkingCapitalLoanProductDeleteFailure(Long productId) { + CallFailedRuntimeException exception = fail( + () -> workingCapitalApi().retrieveOneWorkingCapitalLoanProduct(productId, Map.of())); + assertThat(exception.getStatus()).as(ErrorMessageHelper.dateFailureErrorCodeMsg()).isEqualTo(404); + assertThat(exception.getDeveloperMessage()) + .contains(ErrorMessageHelper.workingCapitalLoanProductIdentifiedDoesNotExistFailure(String.valueOf(productId))); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/reporting/ReportingStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/reporting/ReportingStepDef.java new file mode 100644 index 00000000000..e7b26a0756d --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/reporting/ReportingStepDef.java @@ -0,0 +1,136 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.stepdef.reporting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Then; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.PostOfficesResponse; +import org.apache.fineract.client.models.ResultsetColumnHeaderData; +import org.apache.fineract.client.models.RunReportsResponse; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; + +@RequiredArgsConstructor +public class ReportingStepDef extends AbstractStepDef { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.ENGLISH); + + private final FineractFeignClient fineractClient; + + @Then("Transaction Summary Report for date {string} has the following data:") + public void transactionSummaryReportHasData(final String dateStr, final DataTable dataTable) { + verifyReportData("Transaction Summary Report", dateStr, dataTable); + } + + @Then("Transaction Summary Report with Asset Owner for date {string} has the following data:") + public void transactionSummaryReportWithAssetOwnerHasData(final String dateStr, final DataTable dataTable) { + verifyReportData("Transaction Summary Report with Asset Owner", dateStr, dataTable); + } + + private void verifyReportData(final String reportName, final String dateStr, final DataTable dataTable) { + final PostOfficesResponse officeResponse = testContext().get(TestContextKey.OFFICE_CREATE_RESPONSE); + assertThat(officeResponse).as("No office was created. Use 'Admin creates a new office' step first.").isNotNull(); + + final String date = LocalDate.parse(dateStr, FORMATTER).toString(); + final RunReportsResponse response = fineractClient.runReports().runReportGetData(reportName, Map.of("R_endDate", date, "R_officeId", + String.valueOf(officeResponse.getOfficeId()), "locale", "en", "dateFormat", "yyyy-MM-dd")); + assertThat(response.getData()).as("Report '%s' returned no data", reportName).isNotNull(); + + final List> expected = dataTable.asLists(); + final List headers = expected.getFirst(); + + assertThat(response.getColumnHeaders()).isNotNull(); + final int[] colIdx = headers.stream().mapToInt(h -> findColumnIndex(response.getColumnHeaders(), h)).toArray(); + + final List> actual = response.getData().stream().map(row -> { + assertThat(row.getRow()).as("Report '%s' returned a row with null cell list", reportName).isNotNull(); + return IntStream.of(colIdx).mapToObj(i -> { + assertThat(i).as("Report '%s': column index %d is out of bounds (row size: %d)", reportName, i, row.getRow().size()) + .isLessThan(row.getRow().size()); + return stringify(row.getRow().get(i)); + }).toList(); + }).toList(); + + assertThat(actual).as("Report '%s' row count mismatch.\nActual rows:\n%s", reportName, formatRows(actual)) + .hasSize(expected.size() - 1); + + for (int i = 1; i < expected.size(); i++) { + final List expRow = expected.get(i).stream().map(v -> v == null ? "" : v).toList(); + final List actRow = actual.get(i - 1); + for (int j = 0; j < headers.size(); j++) { + if (expRow.get(j).isEmpty()) { + continue; + } + if (!valuesMatch(expRow.get(j), actRow.get(j))) { + fail("Report '%s', row %d, column '%s': expected='%s', actual='%s'\nAll actual rows:\n%s", reportName, i, + headers.get(j), expRow.get(j), actRow.get(j), formatRows(actual)); + } + } + } + } + + private boolean valuesMatch(final String expected, final String actual) { + if (Objects.equals(expected, actual)) { + return true; + } + if (isBooleanMatch(expected, actual)) { + return true; + } + try { + return new BigDecimal(expected).compareTo(new BigDecimal(actual)) == 0; + } catch (NumberFormatException e) { + return false; + } + } + + private boolean isBooleanMatch(final String expected, final String actual) { + return ("0".equals(expected) && "false".equals(actual)) || ("false".equals(expected) && "0".equals(actual)) + || ("1".equals(expected) && "true".equals(actual)) || ("true".equals(expected) && "1".equals(actual)); + } + + private String stringify(final Object val) { + return val == null ? "null" : String.valueOf(val); + } + + private String formatRows(final List> rows) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < rows.size(); i++) { + sb.append(String.format(" [%d] %s%n", i + 1, String.join(" | ", rows.get(i)))); + } + return sb.toString(); + } + + private int findColumnIndex(final List headers, final String name) { + return IntStream.range(0, headers.size()).filter(i -> name.equalsIgnoreCase(headers.get(i).getColumnName())).findFirst() + .orElseThrow(() -> new IllegalArgumentException("Column '" + name + "' not found in report")); + } +} diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java index c52c96e4850..1a2243906fe 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/saving/SavingsAccountStepDef.java @@ -50,7 +50,7 @@ public void createSavingsAccountEUR(String submittedOnDate) { .clientId(clientId).submittedOnDate(submittedOnDate); PostSavingsAccountsResponse createSavingsAccountResponse = ok( - () -> fineractClient.savingsAccount().submitApplication2(createSavingsAccountRequest)); + () -> fineractClient.savingsAccount().submitSavingsApplication(createSavingsAccountRequest)); testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_CREATE_RESPONSE, createSavingsAccountResponse); } @@ -63,7 +63,7 @@ public void createSavingsAccountUSD(String submittedOnDate) { .clientId(clientId).submittedOnDate(submittedOnDate); PostSavingsAccountsResponse createSavingsAccountResponse = ok( - () -> fineractClient.savingsAccount().submitApplication2(createSavingsAccountRequest)); + () -> fineractClient.savingsAccount().submitSavingsApplication(createSavingsAccountRequest)); testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_CREATE_RESPONSE, createSavingsAccountResponse); } @@ -76,7 +76,7 @@ public void approveEurSavingsAccount(String approvedOnDate) { .approvedOnDate(approvedOnDate); PostSavingsAccountsAccountIdResponse approveSavingsAccountResponse = ok(() -> fineractClient.savingsAccount() - .handleCommands6(savingsAccountID, approveSavingsAccountRequest, Map.of("command", "approve"))); + .handleCommandsSavingsAccount(savingsAccountID, approveSavingsAccountRequest, Map.of("command", "approve"))); testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_APPROVE_RESPONSE, approveSavingsAccountResponse); } @@ -89,7 +89,7 @@ public void approveUsdSavingsAccount(String approvedOnDate) { .approvedOnDate(approvedOnDate); PostSavingsAccountsAccountIdResponse approveSavingsAccountResponse = ok(() -> fineractClient.savingsAccount() - .handleCommands6(savingsAccountID, approveSavingsAccountRequest, Map.of("command", "approve"))); + .handleCommandsSavingsAccount(savingsAccountID, approveSavingsAccountRequest, Map.of("command", "approve"))); testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_APPROVE_RESPONSE, approveSavingsAccountResponse); } @@ -102,7 +102,7 @@ public void activateSavingsAccount(String activatedOnDate) { .activatedOnDate(activatedOnDate); PostSavingsAccountsAccountIdResponse activateSavingsAccountResponse = ok(() -> fineractClient.savingsAccount() - .handleCommands6(savingsAccountID, activateSavingsAccountRequest, Map.of("command", "activate"))); + .handleCommandsSavingsAccount(savingsAccountID, activateSavingsAccountRequest, Map.of("command", "activate"))); testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_ACTIVATED_RESPONSE, activateSavingsAccountResponse); } @@ -115,7 +115,7 @@ public void activateUsdSavingsAccount(String activatedOnDate) { .activatedOnDate(activatedOnDate); PostSavingsAccountsAccountIdResponse activateSavingsAccountResponse = ok(() -> fineractClient.savingsAccount() - .handleCommands6(savingsAccountID, activateSavingsAccountRequest, Map.of("command", "activate"))); + .handleCommandsSavingsAccount(savingsAccountID, activateSavingsAccountRequest, Map.of("command", "activate"))); testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_ACTIVATED_RESPONSE, activateSavingsAccountResponse); } @@ -128,7 +128,7 @@ public void createEurDeposit(double depositAmount, String depositDate) { .transactionDate(depositDate).transactionAmount(BigDecimal.valueOf(depositAmount)); PostSavingsAccountTransactionsResponse depositResponse = ok(() -> fineractClient.savingsAccountTransactions() - .transaction2(savingsAccountID, depositRequest, Map.of("command", "deposit"))); + .createSavingsAccountTransaction(savingsAccountID, depositRequest, Map.of("command", "deposit"))); testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_DEPOSIT_RESPONSE, depositResponse); } @@ -141,7 +141,7 @@ public void createUsdDeposit(double depositAmount, String depositDate) { .transactionDate(depositDate).transactionAmount(BigDecimal.valueOf(depositAmount)); PostSavingsAccountTransactionsResponse depositResponse = ok(() -> fineractClient.savingsAccountTransactions() - .transaction2(savingsAccountID, depositRequest, Map.of("command", "deposit"))); + .createSavingsAccountTransaction(savingsAccountID, depositRequest, Map.of("command", "deposit"))); testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_DEPOSIT_RESPONSE, depositResponse); } @@ -154,7 +154,7 @@ public void createEurWithdraw(double withdrawAmount, String transcationDate) { .transactionDate(transcationDate).transactionAmount(BigDecimal.valueOf(withdrawAmount)); PostSavingsAccountTransactionsResponse withdrawalResponse = ok(() -> fineractClient.savingsAccountTransactions() - .transaction2(savingsAccountID, withdrawRequest, Map.of("command", "withdrawal"))); + .createSavingsAccountTransaction(savingsAccountID, withdrawRequest, Map.of("command", "withdrawal"))); testContext().set(TestContextKey.EUR_SAVINGS_ACCOUNT_WITHDRAW_RESPONSE, withdrawalResponse); } @@ -167,7 +167,7 @@ public void createUsdWithdraw(double withdrawAmount, String transcationDate) { .transactionDate(transcationDate).transactionAmount(BigDecimal.valueOf(withdrawAmount)); PostSavingsAccountTransactionsResponse withdrawalResponse = ok(() -> fineractClient.savingsAccountTransactions() - .transaction2(savingsAccountID, withdrawRequest, Map.of("command", "withdrawal"))); + .createSavingsAccountTransaction(savingsAccountID, withdrawRequest, Map.of("command", "withdrawal"))); testContext().set(TestContextKey.USD_SAVINGS_ACCOUNT_WITHDRAW_RESPONSE, withdrawalResponse); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index 8a3fb94a642..70ceff1cb7c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -169,6 +169,7 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_NON_MERCHANT = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFeesNonMerchant"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_NON_MERCHANT_CHARGE_OFF_REASON = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFeesNonMerchantWithChargeOffReason"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_CLASSIFICATION_INCOME_MAP = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFeesClassificationIncomeMap"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_FEE_INCOME = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentBuyDownFeesFeeIncome"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyCapitalizedIncomeAdjCustomAlloc"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PMNT_ALLOCATION_CAPITALIZED_INCOME_ADJ_CUSTOM_ALLOC_CLASSIFICATION_INCOME_MAP = "loanProductCreateResponseLP2AdvancedPaymentCapitalizedIncomeAdjCustomAllocClassificationIncomeMao"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisbursalApprovedOVerAppliedPercentageCapitalizedIncome"; @@ -212,6 +213,7 @@ public abstract class TestContextKey { public static final String CHARGE_FOR_LOAN_INSTALLMENT_FEE_FLAT_CREATE_RESPONSE = "ChargeForLoanInstallmentFeeFlatCreateResponse"; public static final String CHARGE_FOR_CLIENT_FIXED_FEE_CREATE_RESPONSE = "ChargeForClientFixedFeeCreateResponse"; public static final String CHARGE_FOR_LOAN_DISBURSEMENT_CHARGE_CREATE_RESPONSE = "ChargeForLoanDisbursementChargeCreateResponse"; + public static final String CHARGE_FOR_LOAN_DISBURSEMENT_PERCENTAGE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanDisbursementPercentageAmountPlusInterestCreateResponse"; public static final String LOAN_RESPONSE = "loanResponse"; public static final String LOAN_REPAYMENT_UNDO_RESPONSE = "loanRepaymentUndoResponse"; public static final String LOAN_CAPITALIZED_INCOME_ADJUSTMENT_UNDO_RESPONSE = "loanCapitalizedIncomeAdjustmentUndoResponse"; @@ -219,6 +221,7 @@ public abstract class TestContextKey { public static final String LOAN_CHARGEBACK_RESPONSE = "loanChargebackResponse"; public static final String LOAN_CHARGE_ADJUSTMENT_RESPONSE = "loanChargeAdjustmentResponse"; public static final String PUT_CURRENCIES_RESPONSE = "putCurrenciesResponse"; + public static final String GET_CURRENCIES_RESPONSE = "getCurrenciesResponse"; public static final String BATCH_API_CALL_RESPONSE = "batchApiCallResponse"; public static final String BATCH_API_CALL_IDEMPOTENCY_KEY = "batchApiIdempotencyKey"; public static final String BATCH_API_CALL_IDEMPOTENCY_KEY_2 = "batchApiIdempotencyKey2"; @@ -311,4 +314,14 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_CHARGEBACK = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTrancheChargeback"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ZERO_CHARGE_OFF = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTrancheZeroIntChargeOff"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ACCELERATE_MATURITY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyMultidisburseFullTermTrancheAccelerateMaturity"; + public static final String OFFICE_CREATE_RESPONSE = "officeCreateResponse"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC = "loanProductCreateResponseLP2DownPaymentAdvancedPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP = "workingCapitalLoanProductCreateResponseWCLP"; + public static final String WC_LOAN_IDS = "wcLoanIds"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateRequestForUpdateWCLP"; + public static final String DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP = "workingCapitalLoanProductCreateResponseForUpdateWCLP"; + public static final String WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST = "workingCapitalLoanProductCreateRequest"; + public static final String WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE = "workingCapitalLoanProductCreateResponse"; + public static final String WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_REQUEST = "workingCapitalLoanProductUpdateRequest"; + public static final String WORKING_CAPITAL_LOAN_PRODUCT_UPDATE_RESPONSE = "workingCapitalLoanProductUpdateResponse"; } diff --git a/fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties b/fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties index 65727e1431a..eb5ef6e178d 100644 --- a/fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties +++ b/fineract-e2e-tests-core/src/test/resources/fineract-test-application.properties @@ -46,3 +46,10 @@ fineract-test.job.delay-in-ms=${POLLING_JOB_DELAY_IN_MS:3000} fineract-test.job.wait-timeout-in-ms=${POLLING_JOB_WAIT_TIMEOUT_IN_MS:120000} fineract-test.client-read-timeout=${CLIENT_READ_TIMEOUT:60} + +fineract-test.db.protocol=${TESTDB_PROTOCOL:jdbc:postgresql} +fineract-test.db.hostname=${TESTDB_HOSTNAME:localhost} +fineract-test.db.port=${TESTDB_PORT:5432} +fineract-test.db.name=${TESTDB_NAME:fineract_default} +fineract-test.db.username=${TESTDB_USERNAME:postgres} +fineract-test.db.password=${TESTDB_PASSWORD:skdcnwauicn2ucnaecasdsajdnizucawencascdca} diff --git a/fineract-e2e-tests-runner/build.gradle b/fineract-e2e-tests-runner/build.gradle index 223900d2403..6e92c1ef33f 100644 --- a/fineract-e2e-tests-runner/build.gradle +++ b/fineract-e2e-tests-runner/build.gradle @@ -19,7 +19,7 @@ plugins { id 'se.thinkcode.cucumber-runner' version '0.0.11' - id 'io.qameta.allure' version '2.12.0' + id 'io.qameta.allure' version '3.0.2' } apply plugin: 'java' @@ -70,6 +70,10 @@ dependencies { testImplementation 'io.github.classgraph:classgraph:4.8.179' testImplementation 'org.apache.commons:commons-collections4:4.4' + + testImplementation 'org.springframework:spring-jdbc' + testImplementation 'org.postgresql:postgresql' + testImplementation 'org.mariadb.jdbc:mariadb-java-client' } tasks.named('test') { diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/ChargeGlobalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/ChargeGlobalInitializerStep.java index 0a98ddad8da..6dfec136ab7 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/ChargeGlobalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/ChargeGlobalInitializerStep.java @@ -64,6 +64,7 @@ public class ChargeGlobalInitializerStep implements FineractGlobalInitializerSte public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT = "Installment percentage amount fee"; public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_INTEREST = "Installment percentage interest fee"; public static final String CHARGE_LOAN_INSTALLMENT_FEE_PERCENT_AMOUNT_PLUS_INTEREST = "Installment percentage amount + interest fee"; + public static final String CHARGE_LOAN_DISBURSEMENT_PERCENT_AMOUNT_PLUS_INTEREST_FEE = "Disbursement percentage amount + interest fee"; public static final String CHARGE_CLIENT_FIXED_FEE = "Fixed fee for Client"; public static final String CHARGE_DISBURSEMENT_CHARGE = "Disbursement Charge"; public static final String CHARGE_LOAN_TRANCHE_DISBURSEMENT_CHARGE_AMOUNT = "Tranche Disbursement Charge Amount"; @@ -185,6 +186,12 @@ public void initialize() throws Exception { CHARGE_AMOUNT_PERCENTAGE, true, false); TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_INSTALLMENT_FEE_PERCENTAGE_INTEREST_CREATE_RESPONSE, responseLoanInstallmentPercentInterest); + + PostChargesResponse responseLoanDisbursementPercentAmountPlusInterest = createChargeIfNotExists(charges, CHARGE_APPLIES_TO_LOAN, + CHARGE_LOAN_DISBURSEMENT_PERCENT_AMOUNT_PLUS_INTEREST_FEE, CHARGE_TIME_TYPE_DISBURSEMENT, + CHARGE_CALCULATION_TYPE_PERCENTAGE_LOAN_AMOUNT_PLUS_INTEREST, CHARGE_AMOUNT_PERCENTAGE, true, false); + TestContext.INSTANCE.set(TestContextKey.CHARGE_FOR_LOAN_DISBURSEMENT_PERCENTAGE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE, + responseLoanDisbursementPercentAmountPlusInterest); } private PostChargesResponse createChargeIfNotExists(List existingCharges, Enum appliesTo, diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/CodeGlobalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/CodeGlobalInitializerStep.java index 24bb9b6994c..8581bbb2645 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/CodeGlobalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/CodeGlobalInitializerStep.java @@ -260,7 +260,7 @@ public void createCodeValues(String codeName, List codeValueNames) { postCodeValuesDataRequest.position(position); try { - executeVoid(() -> fineractClient.codeValues().createCodeValue1(codeName, postCodeValuesDataRequest, Map.of())); + executeVoid(() -> fineractClient.codeValues().createCodeValueByCodeName(codeName, postCodeValuesDataRequest, Map.of())); log.debug("Code value '{}' created successfully", name); } catch (CallFailedRuntimeException e) { if (e.getStatus() == 403 && e.getDeveloperMessage() != null && e.getDeveloperMessage().contains("already exists")) { @@ -280,7 +280,8 @@ public void updateCodeValues(String codeName, List codeValueNames) { putCodeValuesDataRequest.name(name); putCodeValuesDataRequest.position(position); - executeVoid(() -> fineractClient.codeValues().updateCodeValue1(codeName, (long) position, putCodeValuesDataRequest, Map.of())); + executeVoid(() -> fineractClient.codeValues().updateCodeValueByCodeName(codeName, (long) position, putCodeValuesDataRequest, + Map.of())); }); } diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/FinancialActivityMappingGlobalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/FinancialActivityMappingGlobalInitializerStep.java index a274c6e039a..2f6ca5a493d 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/FinancialActivityMappingGlobalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/FinancialActivityMappingGlobalInitializerStep.java @@ -44,7 +44,8 @@ public void initialize() { .financialActivityId(FINANCIAL_ACTIVITY_ID_ASSET_TRANSFER).glAccountId(GL_ACCOUNT_ID_ASSET_TRANSFER); try { - executeVoid(() -> fineractClient.mappingFinancialActivitiesToAccounts().createGLAccount(request, Map.of())); + executeVoid(() -> fineractClient.mappingFinancialActivitiesToAccounts().createGLAccountMappingFinancialActivityAccount(request, + Map.of())); log.debug("Financial activity mapping created successfully"); } catch (CallFailedRuntimeException e) { if (e.getStatus() == 403 && e.getDeveloperMessage() != null && e.getDeveloperMessage().contains("already exists")) { diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/GLGlobalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/GLGlobalInitializerStep.java index 8be948b3fe5..77399429f28 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/GLGlobalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/GLGlobalInitializerStep.java @@ -141,6 +141,6 @@ private void createGLAccountIfNotExists(List existingAcco } PostGLAccountsRequest request = GLAccountRequestFactory.defaultGLAccountRequest(name, glCode, type, GLA_USAGE_DETAIL, true); - executeVoid(() -> fineractClient.generalLedgerAccount().createGLAccount1(request, Map.of())); + executeVoid(() -> fineractClient.generalLedgerAccount().createGLAccount(request, Map.of())); } } diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index 83ad9bdf299..b44a29d6a41 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -1312,7 +1312,7 @@ public void initialize() throws Exception { .recalculationRestFrequencyType(1)// .recalculationRestFrequencyInterval(1)// .repaymentEvery(1)// - .interestRatePerPeriod((double) 7.0)// + .interestRatePerPeriod(7.0)// .interestRateFrequencyType(INTEREST_RATE_FREQUENCY_TYPE_MONTH)// .enableDownPayment(false)// .interestRecalculationCompoundingMethod(0)// @@ -4585,6 +4585,60 @@ public void initialize() throws Exception { TestContext.INSTANCE.set( TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INT_DAILY_EMI_360_30_INT_RECALC_DAILY_MULTIDISB_FULL_TERM_TRANCHE_ACCELERATE_MATURITY, responseLoanProductsRequestLP2AdvancedpaymentInterestEmi36030InterestRecalcDailyMultiDisburseFullTermTrancheAccelerateMaturity); + + // LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + // + interest recalculation, buy down fees enabled, FEE income type + final String name178 = DefaultLoanProduct.LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME.getName(); + final PostLoanProductsRequest loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome = loanProductsRequestFactory + .defaultLoanProductsRequestLP2BuyDownFeesFeeIncome()// + .name(name178)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// + .loanScheduleType("PROGRESSIVE") // + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + final PostLoanProductsResponse responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome = createLoanProductIdempotent( + loanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_BUYDOWN_FEES_FEE_INCOME, + responseLoanProductsRequestLP2ProgressiveAdvPaymentBuyDownFeesFeeIncome); + + // LP2 with Down-payment + advanced payment allocation + progressive loan schedule + vertical + // (LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL) + String name179 = DefaultLoanProduct.LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC.getName(); + PostLoanProductsRequest loanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc = loanProductsRequestFactory + .defaultLoanProductsRequestLP2Emi()// + .name(name179)// + .loanScheduleProcessingType("VERTICAL").daysInYearType(DaysInYearType.ACTUAL.value)// + .daysInMonthType(DaysInMonthType.ACTUAL.value)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("REPAYMENT", "NEXT_INSTALLMENT"))) // + .chargeOffBehaviour(ACCELERATE_MATURITY.value)// + .multiDisburseLoan(true)// + .disallowExpectedDisbursements(true)// + .allowFullTermForTranche(true)// + .maxTrancheCount(10)// + .maxPrincipal(25000000.0)// + .outstandingLoanBalance(25000000.0);// + PostLoanProductsResponse responseLoanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc = createLoanProductIdempotent( + loanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_DOWNPAYMENT_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC, + responseLoanProductsRequestDownPaymentAdvPaymentAllocationProgressiveLoanScheduleVerticalInterestRecalc); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WcpCobBusinessStepInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WcpCobBusinessStepInitializerStep.java new file mode 100644 index 00000000000..3a4433f9eca --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WcpCobBusinessStepInitializerStep.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.initializer.global; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.JobBusinessStepConfigData; +import org.apache.fineract.test.helper.WorkFlowJobHelper; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class WcpCobBusinessStepInitializerStep implements FineractGlobalInitializerStep { + + private static final String WCP_COB_JOB_NAME = "WORKING_CAPITAL_LOAN_CLOSE_OF_BUSINESS"; + + private final WorkFlowJobHelper workFlowJobHelper; + + @Override + public void initialize() throws Exception { + try { + JobBusinessStepConfigData response = workFlowJobHelper.getConfiguredWorkflowSteps(WCP_COB_JOB_NAME); + log.info("WCP COB configured business steps: {}", response.getBusinessSteps()); + } catch (CallFailedRuntimeException e) { + log.warn("WCP COB business steps retrieval failed (expected if WCP COB not deployed): {}", e.getMessage()); + log.debug("Full stack trace:", e); + } + } +} diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java new file mode 100644 index 00000000000..931e739ad36 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/global/WorkingCapitalInitializerStep.java @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.test.initializer.global; + +import static org.apache.fineract.client.feign.util.FeignCalls.ok; + +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse; +import org.apache.fineract.test.data.workingcapitalproduct.DefaultWorkingCapitalLoanProduct; +import org.apache.fineract.test.factory.WorkingCapitalRequestFactory; +import org.apache.fineract.test.support.TestContext; +import org.apache.fineract.test.support.TestContextKey; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class WorkingCapitalInitializerStep implements FineractGlobalInitializerStep { + + private final FineractFeignClient fineractClient; + private final WorkingCapitalRequestFactory workingCapitalRequestFactory; + + @Override + public void initialize() throws Exception { + + final String workingCapitalProductDefaultName = DefaultWorkingCapitalLoanProduct.WCLP.getName(); + final PostWorkingCapitalLoanProductsRequest defaultWCPLRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductDefaultName); // + final PostWorkingCapitalLoanProductsResponse responseDefaultWCPL = createWorkingCapitalLoanProductIdempotent(defaultWCPLRequest); + TestContext.INSTANCE.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_WCLP, responseDefaultWCPL); + + final String workingCapitalProductForUpdateName = DefaultWorkingCapitalLoanProduct.WCLP_FOR_UPDATE.getName(); + final PostWorkingCapitalLoanProductsRequest defaultForUpdateWCPLRequest = workingCapitalRequestFactory + .defaultWorkingCapitalLoanProductRequest() // + .name(workingCapitalProductForUpdateName); // + final PostWorkingCapitalLoanProductsResponse responseForUpdateWCPL = createWorkingCapitalLoanProductIdempotent( + defaultForUpdateWCPLRequest); + TestContext.GLOBAL.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_REQUEST_FOR_UPDATE_WCLP, + defaultForUpdateWCPLRequest); + TestContext.GLOBAL.set(TestContextKey.DEFAULT_WORKING_CAPITAL_LOAN_PRODUCT_CREATE_RESPONSE_FOR_UPDATE_WCLP, responseForUpdateWCPL); + } + + private PostWorkingCapitalLoanProductsResponse createWorkingCapitalLoanProductIdempotent( + PostWorkingCapitalLoanProductsRequest workingCapitalProductRequest) { + String workingCapitalProductName = workingCapitalProductRequest.getName(); + log.debug("Attempting to create working capital product: {}", workingCapitalProductName); + try { + List existingWorkingCapitalProducts = fineractClient.workingCapitalLoanProducts() + .retrieveAllWorkingCapitalLoanProducts(Map.of()); + GetWorkingCapitalLoanProductsResponse existingWorkingCapitalProduct = existingWorkingCapitalProducts.stream() + .filter(p -> workingCapitalProductName.equals(p.getName())).findFirst().orElse(null); + + if (existingWorkingCapitalProduct != null) { + log.debug("Working capital product '{}' already exists with ID: {}", workingCapitalProductName, + existingWorkingCapitalProduct.getId()); + PostWorkingCapitalLoanProductsResponse response = new PostWorkingCapitalLoanProductsResponse(); + response.setResourceId(existingWorkingCapitalProduct.getId()); + return response; + } + } catch (Exception e) { + log.warn("Error checking if working capital product '{}' exists", workingCapitalProductName, e); + } + + log.debug("Creating new working capital product: {}", workingCapitalProductName); + try { + PostWorkingCapitalLoanProductsResponse response = ok(() -> fineractClient.workingCapitalLoanProducts() + .createWorkingCapitalLoanProduct(workingCapitalProductRequest, Map.of())); + log.debug("Successfully created working capital product '{}' with ID: {}", workingCapitalProductName, response.getResourceId()); + return response; + } catch (Exception e) { + log.error("FAILED to create working capital product '{}'", workingCapitalProductName, e); + throw e; + } + } + +} diff --git a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java index 93ae3321a19..7ffaea59239 100644 --- a/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java +++ b/fineract-e2e-tests-runner/src/test/java/org/apache/fineract/test/initializer/suite/JobSuiteInitializerStep.java @@ -122,7 +122,7 @@ public void resetAfterSuite() { } private Long updateExternalEventJobFrequency(String cronExpression) { - GetJobsResponse externalEventJobResponse = ok(() -> fineractClient.schedulerJob().retrieveAll8()).stream() + GetJobsResponse externalEventJobResponse = ok(() -> fineractClient.schedulerJob().retrieveAllSchedulerJobs()).stream() .filter(r -> r.getDisplayName().equals(SEND_ASYNCHRONOUS_EVENTS_JOB_NAME)).findAny() .orElseThrow(() -> new IllegalStateException(SEND_ASYNCHRONOUS_EVENTS_JOB_NAME + " is not found")); Long jobId = externalEventJobResponse.getJobId(); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Currency.feature b/fineract-e2e-tests-runner/src/test/resources/features/Currency.feature new file mode 100644 index 00000000000..f73854760e0 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Currency.feature @@ -0,0 +1,17 @@ +@CurrencyFeature +Feature: Currency + + @TestRailId:C3963 + Scenario: Verify USD currency properties are correct + When Admin retrieves currency configuration + Then Currency "USD" has the following properties: + | name | symbol | decimalPlaces | + | US Dollar | $ | 2 | + + @TestRailId:C3964 + Scenario: Verify updating selected currencies works correctly + When Admin updates selected currencies to "EUR,KES,BND,LBP,GHC,USD,INR" + Then The returned currency list matches "EUR,KES,BND,LBP,GHC,USD,INR" + When Admin retrieves currency configuration + Then The selected currencies contain "EUR,KES,BND,LBP,GHC,USD,INR" + And Admin resets selected currencies to default diff --git a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature index e50ec3ada92..cf3a1e160c7 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature @@ -7950,7 +7950,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 30 January 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | | 31 January 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | | 01 February 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 882.78 | false | false | - Then Admin fails to disburse the loan on "01 February 2025" with "100" amount + Then Admin fails to disburse the loan on "01 February 2025" with "100" amount due to exceed approved amount # --- undo last disbursement --- # When Admin successfully undo last disbursal Then Loan Tranche Details tab has the following data: @@ -8141,7 +8141,7 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 31 January 2025 | Repayment | 117.86 | 116.16 | 1.7 | 0.0 | 0.0 | 468.02 | false | false | | 31 January 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | | 01 February 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 768.02 | false | false | - Then Admin fails to disburse the loan on "01 February 2025" with "100" amount + Then Admin fails to disburse the loan on "01 February 2025" with "100" amount due to exceed approved amount # --- undo disbursement --- # When Admin sets the business date to "02 February 2025" When Admin runs inline COB job for Loan diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part1.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part1.feature new file mode 100644 index 00000000000..8d2e21c0603 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part1.feature @@ -0,0 +1,931 @@ +@LoanFeature +Feature: Loan - Part1 + + @TestRailId:C16 @Smoke + Scenario: Loan creation functionality in Fineract + When Admin sets the business date to the actual date + When Admin creates a client with random data + When Admin creates a new Loan + + @TestRailId:C17 + Scenario: Loan creation functionality in Fineract + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + + @TestRailId:C42 + Scenario: As a user I would like to see that the loan is not created if the loan submission date is after the business date + When Admin sets the business date to "25 June 2022" + When Admin creates a client with random data + Then Admin fails to create a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + + @TestRailId:C43 + Scenario: As a user I would like to see that the loan is created if the loan submission date is equal to business date + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + + @TestRailId:C46 + Scenario: As a user I would like to see that the loan is approved at the business date + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" + + @TestRailId:C30 @single + Scenario: As a user I would like to see that the loan is cannot be approved with future approval date + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + Then Admin fails to approve the loan on "2 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" because of wrong date + + @TestRailId:C47 @multi + Scenario: As a user I would like to see that the loan can be disbursed at the business date + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" + When Admin successfully disburse the loan on "1 July 2022" with "5000" EUR transaction amount + + @TestRailId:C31 + Scenario: As a user I would like to see that the loan is cannot be disbursed with future disburse date + When Admin sets the business date to "1 July 2022" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 + And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" + Then Admin fails to disburse the loan on "2 July 2022" with "5000" EUR transaction amount because of wrong date + + @TestRailId:C64 + Scenario: As a user I would like to see that 50% over applied amount can be approved and disbursed on loan correctly + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" + When Admin successfully disburse the loan on "1 September 2022" with "1500" EUR transaction amount + + @TestRailId:C65 + Scenario: As a user I would like to see that 50% over applied amount can be approved but more than 50% cannot be disbursed on loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" + Then Admin fails to disburse the loan on "1 September 2022" with "1501" EUR transaction amount because of wrong amount + + @TestRailId:C66 + Scenario: As a user I would like to see that more than 50% over applied amount can not be approved on loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + Then Admin fails to approve the loan on "1 September 2022" with "1501" amount and expected disbursement date on "1 September 2022" because of wrong amount + + @TestRailId:C2769 + Scenario: As a user I would like to see that more than 50% over applied amount in total can not be disbursed on loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" + And Admin successfully disburse the loan on "1 September 2022" with "1400" EUR transaction amount + Then Admin fails to disburse the loan on "1 September 2022" with "101" EUR transaction amount because of wrong amount + + @TestRailId:C3767 + Scenario: Verify disbursed amount exceeds approved over applied amount for progressive loan with percentage overAppliedCalculationType + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1300" amount and expected disbursement date on "1 September 2022" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 1500 + And Admin successfully disburse the loan on "1 September 2022" with "1200" EUR transaction amount + Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 + Then Admin fails to disburse the loan on "1 September 2022" with "301" EUR transaction amount because of wrong amount + + When Loan Pay-off is made on "1 September 2022" + Then Loan's all installments have obligations met + + @TestRailId:C3768 + Scenario: Verify approved amount exceeds approved over applied amount for progressive loan with flat overAppliedCalculationType + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_FLAT_CAPITALIZED_INCOME | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + Then Admin fails to approve the loan on "1 January 2024" with "2001" amount and expected disbursement date on "1 January 2024" because of wrong amount + + And Admin successfully rejects the loan on "1 January 2024" + Then Loan status will be "REJECTED" + + @TestRailId:C3769 + Scenario: Verify disbursed amount exceeds approved over applied amount for progressive loan with flat overAppliedCalculationType + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "1 January 2024" with "9000" amount and expected disbursement date on "1 January 2024" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 11000 + And Admin successfully disburse the loan on "1 January 2024" with "9900" EUR transaction amount + Then Loan has availableDisbursementAmountWithOverApplied field with value: 1100 + Then Admin fails to disburse the loan on "1 January 2024" with "1200" EUR transaction amount because of wrong amount + + When Loan Pay-off is made on "1 January 2024" + Then Loan's all installments have obligations met + + @TestRailId:C3895 + Scenario: Verify disbursed amount approved over applied amount for progressive loan that expects tranches with percentage overAppliedCalculationType - UC1 + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | + And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 + Then Loan status will be "APPROVED" + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + When Admin sets the business date to "2 January 2024" + And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + And Admin checks available disbursement amount 0.0 EUR + Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 + Then Admin fails to disburse the loan on "2 January 2024" with "1600" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "2 January 2024" with "1300" EUR transaction amount + Then Loan has availableDisbursementAmountWithOverApplied field with value: 0 + Then Loan status will be "ACTIVE" + And Admin checks available disbursement amount 0.0 EUR + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | 02 January 2024 | 1300.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + + When Loan Pay-off is made on "2 January 2024" + Then Loan's all installments have obligations met + + @TestRailId:C3896 + Scenario: Verify disbursed amount approved over applied amount for progressive loan that expects tranches with percentage overAppliedCalculationType - UC2 + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | + And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 + Then Loan status will be "APPROVED" + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + When Admin sets the business date to "2 January 2024" + And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + And Admin checks available disbursement amount 0.0 EUR + Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 + Then Admin fails to disburse the loan on "2 January 2024" with "1600" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "2 January 2024" with "1100" EUR transaction amount + Then Loan status will be "ACTIVE" + And Admin checks available disbursement amount 100.0 EUR + Then Loan has availableDisbursementAmountWithOverApplied field with value: 200 + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | 02 January 2024 | 1100.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + + When Loan Pay-off is made on "2 January 2024" + Then Loan's all installments have obligations met + + @TestRailId:C67 + Scenario: As admin I would like to check that amounts are distributed equally in loan repayment schedule + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + Then Amounts are distributed equally in loan repayment schedule in case of total amount 1000 + When Admin successfully disburse the loan on "1 September 2022" with "900" EUR transaction amount + Then Amounts are distributed equally in loan repayment schedule in case of total amount 900 + + @TestRailId:C68 + Scenario: As admin I would like to be sure that approval of on loan can be undone + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + Then Admin can successfully undone the loan approval + + @TestRailId:C69 + Scenario: As admin I would like to be sure that disbursal of on loan can be undone + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount + Then Admin can successfully undone the loan disbursal + Then Admin can successfully undone the loan approval + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + + @TestRailId:C70 + Scenario: As admin I would like to be sure that submitted on date can be edited on loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 September 2022" + Then Admin can successfully modify the loan and changes the submitted on date to "31 August 2022" + + @TestRailId:C2454 @fraud + Scenario: As admin I would like to set Fraud flag to a loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount + Then Admin can successfully set Fraud flag to the loan + + @TestRailId:C2455 @fraud + Scenario: As admin I would like to unset Fraud flag to a loan + When Admin sets the business date to "1 September 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" + When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + + + @TestRailId:C2456 @fraud + Scenario: As admin I would like to try to add fraud flag on a not active loan + When Admin sets the business date to "25 October 2022" + When Admin creates a client with random data + When Admin successfully creates a new customised Loan submitted on date: "25 October 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 + And Admin successfully approves the loan on "25 October 2022" with "1000" amount and expected disbursement date on "25 October 2022" + Then Admin can successfully unset Fraud flag to the loan + + @TestRailId:C2473 @idempotency + Scenario: As admin I would like to verify that idempotency APIs can be called with the Idempotency-Key header + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + Then Loan has 1 "DISBURSEMENT" transactions on Transactions tab + Then Loan has 1 "REPAYMENT" transactions on Transactions tab + + @TestRailId:C2474 @idempotency + Scenario: As admin I would like to verify that idempotency APIs can be called without the Idempotency-Key header + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and system-generated Idempotency key + Then Loan has 1 "DISBURSEMENT" transactions on Transactions tab + Then Loan has 1 "REPAYMENT" transactions on Transactions tab + + @TestRailId:C2475 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of REPAYMENT transaction + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 200 EUR value for transaction amount + Then Transaction response has the correct clientId and the loanId of the first transaction + Then Loan has 1 "REPAYMENT" transactions on Transactions tab + + @TestRailId:C2476 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of GOODWILL_CREDIT transaction + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 1000 EUR transaction amount and system-generated Idempotency key + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 200 EUR value for transaction amount + Then Transaction response has the correct clientId and the loanId of the first transaction + Then Loan has 1 "GOODWILL_CREDIT" transactions on Transactions tab + + @TestRailId:C2477 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of PAYOUT_REFUND transaction + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 200 EUR value for transaction amount + Then Transaction response has the correct clientId and the loanId of the first transaction + Then Loan has 1 "PAYOUT_REFUND" transactions on Transactions tab + + @TestRailId:C2478 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of MERCHANT_ISSUED_REFUND transaction + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 200 EUR value for transaction amount + Then Transaction response has the correct clientId and the loanId of the first transaction + Then Loan has 1 "MERCHANT_ISSUED_REFUND" transactions on Transactions tab + + @TestRailId:C2482 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in case of client calls the same idempotency key on a second loan + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin crates a second default loan with date: "1 November 2022" + And Admin successfully approves the second loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the second loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "REPAYMENT" transaction on the second loan with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 200 EUR value for transaction amount + Then Transaction response has the correct clientId and the loanId of the first transaction + Then Loan has 1 "REPAYMENT" transactions on Transactions tab + Then Second loan has 0 "REPAYMENT" transactions on Transactions tab + +# TODO unskip and check when PS-1106 is done + @Skip @TestRailId:C2483 @idempotency + Scenario: As admin I would like to verify that idempotency applies correctly in case of a second client calls the same idempotency key on a second loan + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin creates a second client with random data + When Admin crates a second default loan for the second client with date: "1 November 2022" + And Admin successfully approves the second loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the second loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + And Customer makes "REPAYMENT" transaction on the second loan with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction + Then Transaction response has boolean value in header "x-served-from-cache": "true" + Then Transaction response has 300 EUR value for transaction amount + Then Transaction response has the clientId for the second client and the loanId of the second transaction + Then Loan has 1 "REPAYMENT" transactions on Transactions tab + Then Second loan has 1 "REPAYMENT" transactions on Transactions tab + + @TestRailId:C2479 + Scenario: As admin I would like to be sure that goodwill credit transaction is working properly + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + Then Loan has 1 "GOODWILL_CREDIT" transactions on Transactions tab + + @TestRailId:C2480 + Scenario: As admin I would like to be sure that payout refund transaction is working properly + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + Then Loan has 1 "PAYOUT_REFUND" transactions on Transactions tab + + @TestRailId:C2481 + Scenario: As admin I would like to be sure that merchant issued refund transaction is working properly + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "15 November 2022" + And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key + Then Loan has 1 "MERCHANT_ISSUED_REFUND" transactions on Transactions tab + + @TestRailId:C2488 + Scenario: As admin I would like to be sure that no multiple status change event got raised during transaction replaying + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + Then Loan status has changed to "Approved" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + Then Loan status has changed to "Active" + When Admin sets the business date to "2 November 2022" + And Customer makes "AUTOPAY" repayment on "2 November 2022" with 500 EUR transaction amount + When Admin sets the business date to "3 November 2022" + And Customer makes "AUTOPAY" repayment on "3 November 2022" with 100 EUR transaction amount + When Admin sets the business date to "4 November 2022" + And Customer makes "AUTOPAY" repayment on "4 November 2022" with 600 EUR transaction amount + Then Loan status has changed to "Overpaid" + When Customer undo "2"th repayment on "4 November 2022" + Then No new event with type "LoanStatusChangedEvent" has been raised for the loan + When Customer undo "1"th repayment on "4 November 2022" + Then Loan status has changed to "Active" + + @TestRailId:C2489 + Scenario: As admin I would like to charge-off a loan and be sure the event was triggered + When Admin sets the business date to "1 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 November 2022" + And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" + When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount + When Admin sets the business date to "2 November 2022" + And Customer makes "AUTOPAY" repayment on "2 November 2022" with 500 EUR transaction amount + When Admin sets the business date to "3 November 2022" + And Admin does charge-off the loan on "3 November 2022" + Then Loan marked as charged-off on "03 November 2022" + + @TestRailId:C2491 + Scenario: As a user I would like to do multiple repayment, overpay the loan and reverse-replaying transactions and check outstanding balance + When Admin sets the business date to "01 November 2022" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 November 2022" + And Admin successfully approves the loan on "01 November 2022" with "1000" amount and expected disbursement date on "01 November 2022" + When Admin successfully disburse the loan on "01 November 2022" with "1000" EUR transaction amount + Then Loan has 1000 outstanding amount + When Admin sets the business date to "02 November 2022" + And Customer makes "AUTOPAY" repayment on "02 November 2022" with 500 EUR transaction amount + Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan has 500 outstanding amount + When Admin sets the business date to "03 November 2022" + And Customer makes "AUTOPAY" repayment on "03 November 2022" with 10 EUR transaction amount + Then Loan Transactions tab has a transaction with date: "03 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 490.0 | + Then Loan has 490 outstanding amount + When Admin sets the business date to "04 November 2022" + And Customer makes "AUTOPAY" repayment on "04 November 2022" with 400 EUR transaction amount + Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 90.0 | + Then Loan has 90 outstanding amount + When Admin sets the business date to "05 November 2022" + And Customer makes "AUTOPAY" repayment on "05 November 2022" with 390 EUR transaction amount + Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 390.0 | 90.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan has 300 overpaid amount + When Customer undo "2"th repayment on "04 November 2022" + Then In Loan Transactions the "3"th Transaction has Transaction type="Repayment" and is reverted + Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 390.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan has 290 overpaid amount + When Customer undo "1"th repayment on "04 November 2022" + Then In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted + Then In Loan Transactions the "3"th Transaction has Transaction type="Repayment" and is reverted + Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 600.0 | + Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 390.0 | 390.0 | 0.0 | 0.0 | 0.0 | 210.0 | + Then Loan has 210 outstanding amount + And Customer makes "AUTOPAY" repayment on "02 November 2022" with 500 EUR transaction amount + Then Loan has 290 overpaid amount + + @TestRailId:C2502 + Scenario: Verify that Loan status goes from active to overpaid in case of Goodwill credit transaction when transaction amount is greater than balance + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 300 outstanding amount + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 100 overpaid amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 250.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | + Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | l1 | Overpayment account | | 100.0 | + | EXPENSE | 744003 | Goodwill Expense Account | 400.0 | | + + @TestRailId:C2503 + Scenario: Verify that Loan status goes from active to overpaid in case of Backdated 3rd repayment when transaction amount is greater than balance + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 300 outstanding amount + And Customer makes "AUTOPAY" repayment on "2 January 2023" with 400 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 100 overpaid amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "02 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 400.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 400.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 150.0 | + | LIABILITY | l1 | Overpayment account | | 100.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | + + + @TestRailId:C2504 + Scenario: Verify that Loan status goes from overpaid to active in case of Chargeback transaction when transaction amount is greater than overpaid amount + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 200 overpaid amount + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 100 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + | ASSET | 112601 | Loans Receivable | | 100.0 | + | LIABILITY | l1 | Overpayment account | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + Then Loan Transactions tab has a "CHARGEBACK" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 100.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 300.0 | + | LIABILITY | l1 | Overpayment account | 200.0 | | + + @TestRailId:C2506 + Scenario: Verify that Loan status goes from overpaid to active in case of 1st repayment is undone + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 200 overpaid amount + When Customer undo "1"th repayment on "3 January 2023" + Then Loan status will be "ACTIVE" + Then Loan has 250 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + | ASSET | 112601 | Loans Receivable | 450.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 450.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + + @TestRailId:C2507 + Scenario: Verify that Loan status goes from active to closed in case of Goodwill credit transaction when transaction amount equals balance + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 300 outstanding amount + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 300 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 250.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | + Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | EXPENSE | 744003 | Goodwill Expense Account | 300.0 | | + + @TestRailId:C2508 + Scenario: Verify that Loan status goes from active to closed in case of Backdated 3rd repayment when transaction amount equals balance + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 300 outstanding amount + And Customer makes "AUTOPAY" repayment on "2 January 2023" with 300 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "02 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 250.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | + + @TestRailId:C2509 + Scenario: Verify that Loan status goes from closed to overpaid in case of Goodwill credit transaction + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 500 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + When Admin sets the business date to "7 January 2023" + And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "7 January 2023" with 100 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 100 overpaid amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 500.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 500.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | + Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "07 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | LIABILITY | l1 | Overpayment account | | 100.0 | + | EXPENSE | 744003 | Goodwill Expense Account | 100.0 | | + + + @TestRailId:C2510 + Scenario: Verify that Loan status goes from overpaid to closed in case of Chargeback transaction when transaction amount equals overpaid amount + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 200 overpaid amount + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 200 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 450.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | + | ASSET | 112601 | Loans Receivable | | 100.0 | + | LIABILITY | l1 | Overpayment account | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + Then Loan Transactions tab has a "CHARGEBACK" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | LIABILITY | l1 | Overpayment account | 200.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | + + + @TestRailId:C2512 + Scenario: Verify that Loan status goes from overpaid to closed in case of 1st repayment is undone + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 700 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 200 overpaid amount + When Customer undo "1"th repayment on "3 January 2023" + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | + | ASSET | 112601 | Loans Receivable | 200.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 700.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 700.0 | | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + + @TestRailId:C2513 + Scenario: Verify that Loan status goes from closed to active in case of Chargeback transaction + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 500 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount + When Admin sets the business date to "7 January 2023" + And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 200 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 200 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 500.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 500.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 300.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | + Then Loan Transactions tab has a "CHARGEBACK" transaction with date "07 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 200.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | + + @TestRailId:C2514 + Scenario: Verify that Loan status goes from closed to active in case of 1st repayment is undone + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "3 January 2023" + And Customer makes "AUTOPAY" repayment on "3 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "5 January 2023" + And Customer makes "AUTOPAY" repayment on "5 January 2023" with 600 EUR transaction amount + When Admin sets the business date to "7 January 2023" + And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + When Customer undo "1"th repayment on "3 January 2023" + Then Loan status will be "ACTIVE" + Then Loan has 200 outstanding amount + Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | 1000.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | + | ASSET | 112601 | Loans Receivable | 200.0 | | + | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 600.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 600.0 | | + Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 200.0 | + | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | + + + @TestRailId:C2539 + Scenario: Verify that loan overdue calculation is updated upon Goodwill credit transaction + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "1 March 2023" + When Admin runs inline COB job for Loan + Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" + Then Loan status will be "ACTIVE" + Then Loan has 1000 outstanding amount + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key + Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part2.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part2.feature new file mode 100644 index 00000000000..4574122c423 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part2.feature @@ -0,0 +1,2581 @@ +@LoanFeature +Feature: Loan - Part2 + + @TestRailId:C2540 + Scenario: Verify that loan overdue calculation is updated upon Payout refund transaction + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "1 March 2023" + When Admin runs inline COB job for Loan + Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" + Then Loan status will be "ACTIVE" + Then Loan has 1000 outstanding amount + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key + Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C2541 + Scenario: Verify that loan overdue calculation is updated upon Merchant issued refund transaction + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "1 March 2023" + When Admin runs inline COB job for Loan + Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" + Then Loan status will be "ACTIVE" + Then Loan has 1000 outstanding amount + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key + Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C2552 + Scenario: Verify that delinquency event contains the correct delinquentDate in case of one repayment is overdue + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "5 March 2023" + When Admin runs inline COB job for Loan + Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" + + @TestRailId:C2553 + Scenario: Verify that delinquency event contains the correct delinquentDate in case of multiple repayments are overdue + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 1 January 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" + When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "5 April 2023" + When Admin runs inline COB job for Loan + Then Admin checks that delinquency range is: "RANGE_30" and has delinquentDate "2023-02-04" + + @TestRailId:C2583 + Scenario: Verify last payment related fields when retrieving loan details with 1 repayment + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 200.0 | 03 January 2023 | 200.0 | 03 January 2023 | + + @TestRailId:C2586 + Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on different day + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "05 January 2023" + And Customer makes "AUTOPAY" repayment on "05 January 2023" with 300 EUR transaction amount + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 300.0 | 05 January 2023 | 300.0 | 05 January 2023 | + + @TestRailId:C2587 + Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on the same day + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 300 EUR transaction amount + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 300.0 | 03 January 2023 | 300.0 | 03 January 2023 | + + @TestRailId:C2588 + Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on different day then the second repayment reversed + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "05 January 2023" + And Customer makes "AUTOPAY" repayment on "05 January 2023" with 300 EUR transaction amount + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 300.0 | 05 January 2023 | 300.0 | 05 January 2023 | + When Customer undo "1"th transaction made on "05 January 2023" + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 200.0 | 03 January 2023 | 200.0 | 03 January 2023 | + + @TestRailId:C2589 + Scenario: Verify last payment related fields when retrieving loan details with 1 repayment and 1 goodwill credit transaction + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "05 January 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 400.0 | 05 January 2023 | 200.0 | 03 January 2023 | + + @TestRailId:C2590 + Scenario: Verify last payment related fields when retrieving loan details with 1 repayment, 1 goodwill credit transaction and 1 more repayment then the second repayment reversed + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "03 January 2023" + And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount + When Admin sets the business date to "05 January 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "07 January 2023" + And Customer makes "AUTOPAY" repayment on "07 January 2023" with 300 EUR transaction amount + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 300.0 | 07 January 2023 | 300.0 | 07 January 2023 | + When Customer undo "1"th transaction made on "07 January 2023" + Then Loan details has the following last payment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 400.0 | 05 January 2023 | 200.0 | 03 January 2023 | + + @TestRailId:C2678 + Scenario: Verify that after loan is closed loan details and event has last repayment date and amount + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + Then Loan status has changed to "Active" + When Admin sets the business date to "02 January 2023" + And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1000 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan details and event has the following last repayment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 1000.0 | 02 January 2023 | 1000.0 | 02 January 2023 | + + @TestRailId:C2679 + Scenario: Verify that after loan is overpaid loan details and event has last repayment date and amount + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + Then Loan status has changed to "Active" + When Admin sets the business date to "02 January 2023" + And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1100 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan details and event has the following last repayment related data: + | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | + | 1100.0 | 02 January 2023 | 1100.0 | 02 January 2023 | + + @TestRailId:C2687 @fraud + Scenario: Verify that closed loan can be marked as Fraud + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "15 January 2023" + And Customer makes "AUTOPAY" repayment on "15 January 2023" with 1000 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Admin can successfully set Fraud flag to the loan + + @TestRailId:C2688 @fraud + Scenario: Verify that overpaid loan can be marked as Fraud + When Admin sets the business date to "01 January 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 January 2023" + And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" + When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount + When Admin sets the business date to "15 January 2023" + And Customer makes "AUTOPAY" repayment on "15 January 2023" with 1100 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Admin can successfully set Fraud flag to the loan + + @TestRailId:C2690 + Scenario: Verify that the repayment schedule is correct when the loan has a fee and multi disbursement happens + When Admin sets the business date to "1 May 2023" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "1 May 2023", with Principal: "1000", a loanTermFrequency: 1 months, and numberOfRepayments: 1 + And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023" + And Admin successfully disburse the loan on "1 May 2023" with "750" EUR transaction amount + Then Loan has 750 outstanding amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 May 2023" due date and 8 EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 June 2023 | | 0.0 | 750.0 | 0.0 | 8.0 | 0.0 | 758.0 | 0.0 | 0.0 | 0.0 | 758.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 750 | 0 | 8 | 0 | 758 | 0 | 0 | 0 | 758 | + And Admin successfully disburse the loan on "1 May 2023" with "750" EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 June 2023 | | 0.0 | 1500.0 | 0.0 | 8.0 | 0.0 | 1508.0 | 0.0 | 0.0 | 0.0 | 1508.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500 | 0 | 8 | 0 | 1508 | 0 | 0 | 0 | 1508 | + + @TestRailId:C2691 + Scenario: As an admin I would like to do a chargeback for Goodwill Credit + When Admin sets the business date to "8 May 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" + And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount + When Admin sets the business date to "9 May 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "10 May 2023" + And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | + | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 09 May 2023 | Goodwill Credit | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | + | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C2692 + Scenario: As an admin I would like to do a chargeback for Payout Refund + When Admin sets the business date to "8 May 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" + And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount + When Admin sets the business date to "9 May 2023" + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "10 May 2023" + And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | + | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 09 May 2023 | Payout Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | + | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C2693 + Scenario: As an admin I would like to do a chargeback for Merchant Issued Refund + When Admin sets the business date to "8 May 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" + And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount + When Admin sets the business date to "9 May 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key + When Admin sets the business date to "10 May 2023" + And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | + | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 09 May 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | + | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C2770 + Scenario: As an admin I would like to do two merchant issued refund and charge adjustment to close the loan + When Global config "charge-accrual-date" value set to "submitted-date" + When Admin sets the business date to "14 May 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 14 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST | + And Admin successfully approves the loan on "14 May 2023" with "127.95" amount and expected disbursement date on "14 May 2023" + And Admin successfully disburse the loan on "14 May 2023" with "127.95" EUR transaction amount + When Admin sets the business date to "11 June 2023" + When Batch API call with steps: rescheduleLoan from "13 June 2023" to "13 July 2023" submitted on date: "11 June 2023", approveReschedule on date: "11 June 2023" runs with enclosingTransaction: "true" + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "13 July 2023" due date and 3.65 EUR transaction amount + When Admin sets the business date to "12 June 2023" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 0.0 | 0.0 | 0.0 | 131.6 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 127.95 | 0 | 3.65 | 0 | 131.60 | 0 | 0 | 0 | 131.60 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | + | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | + When Admin sets the business date to "17 June 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 June 2023" with 125 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 125.0 | 125.0 | 0.0 | 6.6 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 127.95 | 0 | 3.65 | 0 | 131.6 | 125.0 | 125.0 | 0 | 6.60 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | + | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | + | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | + When Admin makes a charge adjustment for the last "LOAN_SNOOZE_FEE" type charge which is due on "13 July 2023" with 3.65 EUR transaction amount and externalId "" + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 128.65 | 128.65 | 0.0 | 2.95 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 127.95 | 0 | 3.65 | 0 | 131.6 | 128.65 | 128.65 | 0 | 2.95 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | + | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | + | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | + | 17 June 2023 | Charge Adjustment | 3.65 | 2.95 | 0.0 | 0.7 | 0.0 | 0.0 | + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 June 2023" with 2.95 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 60 | 13 July 2023 | 17 June 2023 | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 131.6 | 131.6 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 127.95 | 0 | 3.65 | 0 | 131.6 | 131.6 | 131.6 | 0 | 0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | + | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | + | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | + | 17 June 2023 | Charge Adjustment | 3.65 | 2.95 | 0.0 | 0.7 | 0.0 | 0.0 | + | 17 June 2023 | Merchant Issued Refund | 2.95 | 0.0 | 0.0 | 2.95 | 0.0 | 0.0 | + When Global config "charge-accrual-date" value set to "due-date" + + @TestRailId:C2776 + Scenario: Verify that maturity date is updated on repayment reversal + When Admin sets the business date to "01 June 2023" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 June 2023" + And Admin successfully approves the loan on "01 June 2023" with "1000" amount and expected disbursement date on "01 June 2023" + When Admin successfully disburse the loan on "01 June 2023" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has the following maturity data: + | actualMaturityDate | expectedMaturityDate | + | 01 July 2023 | 01 July 2023 | + When Admin sets the business date to "20 June 2023" + And Customer makes "AUTOPAY" repayment on "20 June 2023" with 1000 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has the following maturity data: + | actualMaturityDate | expectedMaturityDate | + | 20 June 2023 | 01 July 2023 | + When Admin sets the business date to "20 June 2023" + When Customer undo "1"th "Repayment" transaction made on "20 June 2023" + Then Loan status will be "ACTIVE" + Then Loan has the following maturity data: + | actualMaturityDate | expectedMaturityDate | + | 01 July 2023 | 01 July 2023 | + + @TestRailId:C3202 + Scenario: Verify that closed date is updated on repayment reversal + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a new default Loan with date: "01 June 2024" + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + When Admin sets the business date to "20 June 2024" + And Customer makes "AUTOPAY" repayment on "20 June 2024" with 1000 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan closedon_date is "20 June 2024" + When Admin sets the business date to "21 June 2024" + When Customer undo "1"th "Repayment" transaction made on "20 June 2024" + Then Loan status will be "ACTIVE" + Then Loan closedon_date is "null" + + @TestRailId:C2777 + Scenario: As an admin I would like to delete a loan using external id + When Admin sets the business date to the actual date + And Admin creates a client with random data + When Admin creates a new Loan + Then Admin successfully deletes the loan with external id + + @TestRailId:C2778 + Scenario: As an admin I would like to verify that deleting loan using incorrect external id gives error + When Admin sets the business date to the actual date + And Admin creates a client with random data + When Admin creates a new Loan + Then Admin fails to delete the loan with incorrect external id + + @TestRailId:C2784 + Scenario: As a user I would like to do multiple repayment after reverse transactions and check the order of transactions + When Admin sets the business date to "01 November 2022" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 01 November 2022 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST | + And Admin successfully approves the loan on "01 November 2022" with "1000" amount and expected disbursement date on "01 November 2022" + When Admin successfully disburse the loan on "01 November 2022" with "1000" EUR transaction amount + Then Loan has 1000 outstanding amount + When Admin adds "LOAN_NSF_FEE" due date charge with "2 November 2022" due date and 10 EUR transaction amount + When Admin sets the business date to "02 November 2022" + And Customer makes "AUTOPAY" repayment on "02 November 2022" with 9 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "02 November 2022" with 8 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "02 November 2022" with 7 EUR transaction amount + Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | + | Repayment | 8.0 | 7.0 | 0.0 | 0.0 | 1.0 | 993.0 | + | Repayment | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | 986.0 | + When Customer undo "1"th repayment on "02 November 2022" + Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | + | Repayment | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 1000.0 | + | Repayment | 7.0 | 5.0 | 0.0 | 0.0 | 2.0 | 993.0 | + When Customer undo "2"th repayment on "02 November 2022" + Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: + | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | + | Repayment | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 1000.0 | + | Repayment | 7.0 | 0.0 | 0.0 | 0.0 | 7.0 | 1000.0 | + + @TestRailId:C2783 + Scenario: As an admin I would like to verify that only one active repayment schedule exits for loan multiple disbursement + When Admin sets the business date to "07 July 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 07 July 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "07 July 2023" with "1000" amount and expected disbursement date on "07 July 2023" + And Admin successfully disburse the loan on "07 July 2023" with "370.55" EUR transaction amount + When Admin sets the business date to "12 July 2023" + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 July 2023" due date and 5.15 EUR transaction amount + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 06 August 2023 | | 0.0 | 370.55 | 0.0 | 5.15 | 0.0 | 375.7 | 0.0 | 0.0 | 0.0 | 375.7 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 370.55 | 0 | 5.15 | 0 | 375.70 | 0 | 0 | 0 | 375.70 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | + | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | + When Admin sets the business date to "21 July 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "21 July 2023" with 167.4 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 06 August 2023 | | 0.0 | 370.55 | 0.0 | 5.15 | 0.0 | 375.7 | 167.4 | 167.4 | 0.0 | 208.3 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 370.55 | 0 | 5.15 | 0 | 375.7 | 167.4 | 167.4 | 0 | 208.3 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | + | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | + | 21 July 2023 | Merchant Issued Refund | 167.4 | 162.25 | 0.0 | 5.15 | 0.0 | 208.3 | + When Admin runs inline COB job for Loan + When Admin sets the business date to "24 July 2023" + And Admin successfully disburse the loan on "24 July 2023" with "18" EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 24 July 2023 | | 18.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 06 August 2023 | | 0.0 | 388.55 | 0.0 | 5.15 | 0.0 | 393.7 | 167.4 | 167.4 | 0.0 | 226.3 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 388.55 | 0 | 5.15 | 0 | 393.7 | 167.4 | 167.4 | 0 | 226.3 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | + | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | + | 21 July 2023 | Merchant Issued Refund | 167.4 | 162.25 | 0.0 | 5.15 | 0.0 | 208.3 | + | 24 July 2023 | Disbursement | 18.0 | 0.0 | 0.0 | 0.0 | 0.0 | 226.3 | + + @TestRailId:C2842 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that simple payments are working with advanced payment allocation (UC1) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 250.0 | 0 | 0 | 250.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 375.0 | 0 | 0 | 125.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 15 February 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2843 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that simple payments and overpayment of the installment (goes to next installment) are working with advanced payment allocation (UC2) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 275.0 | 25.0 | 0 | 225.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 50.0 | 0 | 100.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 50.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 February 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2844 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that simple payments and overpayment of the installment (goes to last installment) are working with advanced payment allocation (UC3) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "16 January 2023" with 150 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 275.0 | 25.0 | 0 | 225.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + When Admin sets the business date to "31 January 2023" + And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "31 January 2023" with 125 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 25.0 | 0 | 100.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + | 31 January 2023 | Goodwill Credit | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | + When Admin sets the business date to "15 February 2023" + And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 February 2023" with 100 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 25.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | + | 31 January 2023 | Goodwill Credit | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 February 2023 | Goodwill Credit | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2845 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that simple payments are working after some of them failed with advanced payment allocation (UC4) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Customer undo "2"th transaction made on "01 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 125.0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | false | + When Customer undo "1"th transaction made on "16 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "20 January 2023" + And Customer makes "AUTOPAY" repayment on "20 January 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 0.0 | 100.0 | 25.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 100.0 | 0 | 100.0 | 400.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 40 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 31 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 15.0 | 0.0 | 15.0 | 110.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 140.0 | 0 | 140.0 | 360.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | + | 31 January 2023 | Repayment | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 360.0 | false | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 360 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 31 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 15 February 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 15 February 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 0 | 375.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | + | 31 January 2023 | Repayment | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 360.0 | false | + | 15 February 2023 | Repayment | 360.0 | 360.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2846 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that Merchant issued refund with reamortization works with advanced payment allocation (UC05) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Customer undo "2"th transaction made on "01 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "08 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "08 January 2023" with 300 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 08 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.33 | 58.33 | 0.0 | 66.67 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.33 | 58.33 | 0.0 | 66.67 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.34 | 58.34 | 0.0 | 66.66 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 300.0 | 175.0 | 125.0 | 200.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 08 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 201 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 08 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 58.33 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 308.33 | 125.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Overpayment | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | 0.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | 0.0 | + | 08 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | 0.0 | + | 16 January 2023 | Repayment | 201.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | 1.0 | + Then Loan status has changed to "Overpaid" + + @TestRailId:C2847 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that Merchant issued refund with reamortization on due date works with advanced payment allocation (UC07) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 250.0 | 0.0 | 0.0 | 250.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + When Admin sets the business date to "16 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "16 January 2023" with 200 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 200.0 | 0.0 | 50.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 25 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 475.0 | 200.0 | 0.0 | 25.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | + | 31 January 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 25 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 200.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | + | 31 January 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | + | 15 February 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2848 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that Merchant issued refund with reamortization past due date works with advanced payment allocation (UC08) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Customer undo "2"th transaction made on "01 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 125.0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | false | + When Customer undo "1"th transaction made on "16 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "17 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 January 2023" with 300 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 300.0 | 50.0 | 250.0 | 200.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 50.0 | 250.0 | 100.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | + | 31 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 50.0 | 250.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | + | 31 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 15 February 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2849 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that full refund with CBR (UC17) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin sets the business date to "08 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "08 January 2023" with 500 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 08 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 08 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 08 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | + | 08 January 2023 | Merchant Issued Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan status has changed to "Overpaid" + When Admin sets the business date to "09 January 2023" + When Admin makes Credit Balance Refund transaction on "09 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 08 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 08 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 08 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 08 January 2023 | Merchant Issued Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 09 January 2023 | Credit Balance Refund | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2850 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that reverse-replay works with advanced payment allocation(UC24) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Customer undo "2"th transaction made on "01 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "02 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "02 January 2023" with 400 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 275.0 | 125.0 | 100.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | + When Admin sets the business date to "04 January 2023" + And Customer makes "AUTOPAY" repayment on "04 January 2023" with 50 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 108.34 | 108.34 | 0.0 | 16.66 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 325.0 | 125.0 | 50.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 125.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | false | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 0.0 | false | + | 16 January 2023 | Repayment | 125.0 | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | + Then Loan status has changed to "Overpaid" + When Admin sets the business date to "18 January 2023" + When Admin makes Credit Balance Refund transaction on "18 January 2023" with 75 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 125.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | false | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 0.0 | false | + | 16 January 2023 | Repayment | 125.0 | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | + | 18 January 2023 | Credit Balance Refund | 75.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | + Then Loan status has changed to "Closed (obligations met)" + When Admin sets the business date to "20 January 2023" + When Customer undo "1"th transaction made on "02 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 50.0 | 0.0 | 0.0 | 75.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 575.0 | 0 | 0.0 | 0 | 575.0 | 175.0 | 0.0 | 125.0 | 400.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | + | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | + Then Loan status has changed to "Active" + When Admin sets the business date to "31 January 2023" + And Customer makes "AUTOPAY" repayment on "31 January 2023" with 275 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 31 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 75.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 575.0 | 0 | 0.0 | 0 | 575.0 | 450.0 | 0.0 | 200.0 | 125.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | + | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | + | 31 January 2023 | Repayment | 275.0 | 275.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | false | + When Admin sets the business date to "15 February 2023" + And Customer makes "AUTOPAY" repayment on "15 February 2023" with 125 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 31 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 75.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 575.0 | 0 | 0.0 | 0 | 575.0 | 575.0 | 0.0 | 200.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | + | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | + | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | + | 31 January 2023 | Repayment | 275.0 | 275.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | false | + | 15 February 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | + Then Loan status has changed to "Closed (obligations met)" + + @TestRailId:C2851 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that reamortization works with uneven balances with advanced payment allocation(UC25) + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Customer undo "2"th transaction made on "01 January 2023" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + When Admin sets the business date to "02 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "02 January 2023" with 400 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 275.0 | 125.0 | 100.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + When Admin sets the business date to "04 January 2023" + And Customer makes "AUTOPAY" repayment on "04 January 2023" with 50 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 108.34 | 108.34 | 0.0 | 16.66 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 325.0 | 125.0 | 50.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | false | + When Admin sets the business date to "06 January 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "06 January 2023" with 40 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 06 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 115.0 | 115.0 | 0.0 | 10.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 0.0 | 0 | 500.0 | 490.0 | 365.0 | 125.0 | 10.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | + | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | false | + | 06 January 2023 | Merchant Issued Refund | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 10.0 | false | + + @TestRailId:C2860 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: NEXT_INSTALLMENT + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2861 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment on due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2862 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment before due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "10 September 2023" + And Customer makes "AUTOPAY" repayment on "10 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + | 4 | 15 | 16 October 2023 | 10 September 2023 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 100.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 150.0 | 0.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 10 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2863 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, payment on due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 25.0 | 25.0 | 0.0 | 75.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 25.0 | 25.0 | 0.0 | 75.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2864 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, payment before due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "10 September 2023" + And Customer makes "AUTOPAY" repayment on "10 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 150.0 | 0.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 10 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2865 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, partial payment due date, payment before next due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 80 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 0.0 | 0.0 | 20.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 180.0 | 0.0 | 0.0 | 220.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 16 September 2023 | Repayment | 80.0 | 80.0 | 0.0 | 0.0 | 0.0 | 220.0 | + When Admin sets the business date to "20 September 2023" + And Customer makes "AUTOPAY" repayment on "20 September 2023" with 180 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 20 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 20.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 80.0 | 0.0 | 20.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 80.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 360.0 | 160.0 | 20.0 | 40.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 16 September 2023 | Repayment | 80.0 | 80.0 | 0.0 | 0.0 | 0.0 | 220.0 | + | 20 September 2023 | Repayment | 180.0 | 180.0 | 0.0 | 0.0 | 0.0 | 40.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2897 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment after due date + When Admin sets the business date to "01 September 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" + And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin sets the business date to "20 September 2023" + And Customer makes "AUTOPAY" repayment on "20 September 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 20 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 100.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 100.0 | 150.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 20 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2922 + @ProgressiveLoanSchedule + @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation with progressive loan schedule with multi disbursement and with overpaid installment + When Admin sets the business date to "01 May 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 May 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 60 | DAYS | 15 | DAYS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 May 2023" with "1000" amount and expected disbursement date on "01 May 2023" + And Admin successfully disburse the loan on "01 May 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 May 2023 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 3 | 15 | 31 May 2023 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "06 May 2023" + And Customer makes "AUTOPAY" repayment on "06 May 2023" with 650 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | 3 | 15 | 31 May 2023 | 06 May 2023 | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 25.0 | 25.0 | 0.0 | 162.5 | + | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 650.0 | 400.0 | 250.0 | 350.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + When Admin sets the business date to "25 May 2023" + And Admin successfully disburse the loan on "25 May 2023" with "250" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | + | 5 | 15 | 15 June 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | + | 6 | 15 | 30 June 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1250.0 | 0 | 0.0 | 0 | 1250.0 | 650.0 | 400.0 | 250.0 | 600.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | + When Admin sets the business date to "12 June 2023" + And Admin successfully disburse the loan on "12 June 2023" with "250" EUR transaction amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | + | | | 12 June 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 5 | 0 | 12 June 2023 | | 687.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 6 | 15 | 15 June 2023 | | 343.75 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 25.0 | 25.0 | 0.0 | 318.75 | + | 7 | 15 | 30 June 2023 | | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | + | 12 June 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2937 + @ProgressiveLoanSchedule + @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation with progressive loan schedule with multi disbursement and reschedule + When Admin sets the business date to "01 May 2023" + And Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 May 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 60 | DAYS | 15 | DAYS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 May 2023" with "1000" amount and expected disbursement date on "01 May 2023" + And Admin successfully disburse the loan on "01 May 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 May 2023 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 3 | 15 | 31 May 2023 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "06 May 2023" + And Customer makes "AUTOPAY" repayment on "06 May 2023" with 650 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | 3 | 15 | 31 May 2023 | 06 May 2023 | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 25.0 | 25.0 | 0.0 | 162.5 | + | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 650.0 | 400.0 | 250.0 | 350.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + When Admin sets the business date to "25 May 2023" + When Batch API call with steps: rescheduleLoan from "15 June 2023" to "13 July 2023" submitted on date: "25 May 2023", approveReschedule on date: "25 May 2023" runs with enclosingTransaction: "true" + And Admin successfully disburse the loan on "25 May 2023" with "250" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | + | 5 | 43 | 13 July 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | + | 6 | 15 | 28 July 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1250.0 | 0 | 0.0 | 0 | 1250.0 | 650.0 | 400.0 | 250.0 | 600.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | + When Admin sets the business date to "15 July 2023" + And Admin successfully disburse the loan on "15 July 2023" with "250" EUR transaction amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | + | 5 | 43 | 13 July 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | + | | | 15 July 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 6 | 0 | 15 July 2023 | | 437.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 7 | 15 | 28 July 2023 | | 0.0 | 437.5 | 0.0 | 0.0 | 0.0 | 437.5 | 0.0 | 0.0 | 0.0 | 437.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | + | 15 July 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | + When Batch API call with steps: rescheduleLoan from "13 July 2023" to "13 August 2023" submitted on date: "15 July 2023", approveReschedule on date: "15 July 2023" runs with enclosingTransaction: "true" + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | + | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | + | | | 15 July 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 5 | 0 | 15 July 2023 | | 687.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | + | 6 | 74 | 13 August 2023 | | 343.75 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 25.0 | 25.0 | 0.0 | 318.75 | + | 7 | 15 | 28 August 2023 | | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | + | 15 July 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C2940 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, loan fully paid in advance + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Loan got fully paid in advance + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1020 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | + | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2941 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, loan fully paid in advance + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Loan got fully paid in advance + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1020 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | + | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2942 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, loan overpaid in advance + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Loan got overpaid in advance + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1120 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 100 overpaid amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 1120.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | + | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2943 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, loan overpaid in advance + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Loan got overpaid in advance + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1120 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 100 overpaid amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | + | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 1120.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | + | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2944 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced repayment (future installment type: NEXT_INSTALLMENT) + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advance payment (future installment type: NEXT_INSTALLMENT) + When Admin sets the business date to "17 September 2023" + And Customer makes "AUTOPAY" repayment on "17 September 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 100.0 | 100.0 | 0.0 | 170.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 600.0 | 100.0 | 0.0 | 460.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Repayment | 100.0 | 80.0 | 0.0 | 0.0 | 20.0 | 420.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2945 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced repayment (future installment type: NEXT_INSTALLMENT) + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advance payment (future installment type: NEXT_INSTALLMENT) + When Admin sets the business date to "17 September 2023" + And Customer makes "AUTOPAY" repayment on "17 September 2023" with 100 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 60.0 | 60.0 | 0.0 | 210.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 20.0 | 20.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 600.0 | 100.0 | 0.0 | 460.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Repayment | 100.0 | 40.0 | 0.0 | 0.0 | 60.0 | 460.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part3.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part3.feature new file mode 100644 index 00000000000..88e4a14f8dc --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part3.feature @@ -0,0 +1,2919 @@ +@LoanFeature +Feature: Loan - Part3 + + @TestRailId:C2946 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount < past due charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 15 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 515.0 | 0.0 | 15.0 | 545.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + + @TestRailId:C2947 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount < past due charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 15 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 515.0 | 0.0 | 15.0 | 545.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + + @TestRailId:C2948 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > first past due charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 35.0 | 0.0 | 35.0 | 235.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 0.0 | 35.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 35.0 | 15.0 | 0.0 | 0.0 | 20.0 | 485.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2949 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > first past due charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 20.0 | 0.0 | 20.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 0.0 | 35.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2950 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > sum past due charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 350 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 17 October 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 270.0 | 0.0 | 270.0 | 0.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 80.0 | 0.0 | 80.0 | 190.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 850.0 | 0.0 | 350.0 | 210.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 350.0 | 310.0 | 0.0 | 0.0 | 40.0 | 190.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2951 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > sum past due charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 October 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 350 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | 17 October 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 270.0 | 0.0 | 270.0 | 0.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 80.0 | 0.0 | 80.0 | 190.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 850.0 | 0.0 | 350.0 | 210.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 October 2023 | Goodwill Credit | 350.0 | 310.0 | 0.0 | 0.0 | 40.0 | 190.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + + @TestRailId:C2952 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Goodwill credit transaction on first charge due date (future installment type: LAST_INSTALLMENT) - amount > due date charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 September 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | + | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + + @TestRailId:C2953 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Goodwill credit transaction on first charge due date (future installment type: LAST_INSTALLMENT) - amount > due date charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Admin sets the business date to "17 September 2023" + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | + | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + + @TestRailId:C2954 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Goodwill credit transaction before first charge due date (future installment type: LAST_INSTALLMENT) - amount > first and second charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + When Admin sets the business date to "17 September 2023" + # Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | + | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + + @TestRailId:C2955 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Goodwill credit transaction before first charge due date (future installment type: LAST_INSTALLMENT) - amount > first and second charge + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + When Admin sets the business date to "17 September 2023" +# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) + When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | + | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + + @TestRailId:C2956 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Merchant issued refund transaction on first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) + When Admin sets the business date to "17 September 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + + @TestRailId:C2957 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Merchant issued refund transaction on first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) + When Admin sets the business date to "17 September 2023" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + + @TestRailId:C2958 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Merchant issued refund transaction before first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + When Admin sets the business date to "17 September 2023" +# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + + @TestRailId:C2959 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Merchant issued refund transaction before first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | +# Add charge after maturity + When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make due date repayments + And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount + When Admin sets the business date to "16 September 2023" + And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | +# Make charges for the next installments + When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | + When Admin sets the business date to "17 September 2023" +# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | + | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | + + @TestRailId:C2960 @AdvancedPaymentAllocation + Scenario: Verify that rounding in case of multiple installments works properly + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1 | 01 September 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 October 2023 | | 833.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | + | 2 | 31 | 01 November 2023 | | 666.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | + | 3 | 30 | 01 December 2023 | | 499.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | + | 4 | 31 | 01 January 2024 | | 332.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | + | 5 | 31 | 01 February 2024 | | 165.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | + | 6 | 29 | 01 March 2024 | | 0.0 | 165.0 | 0.0 | 0.0 | 0.0 | 165.0 | 0.0 | 0.0 | 0.0 | 165.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C2976 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify that disbursement amount only distributed only to future installments (2nd and 3rd installments) + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "01 October 2023" + When Admin successfully disburse the loan on "01 October 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | | | 01 October 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 October 2023 | | 800.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 31 | 01 November 2023 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 5 | 30 | 01 December 2023 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1400.0 | 0.0 | 50.0 | 0.0 | 1450.0 | 250.0 | 0.0 | 0.0 | 1200.0 | + + @TestRailId:C2977 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify that disbursement amount only distributed only to future installments (1st, 2nd and 3rd installments) + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | + When Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 1150.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 0 | 01 September 2023 | | 1050.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 3 | 30 | 01 October 2023 | | 700.0 | 350.0 | 0.0 | 50.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 4 | 31 | 01 November 2023 | | 350.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | + | 5 | 30 | 01 December 2023 | | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1400.0 | 0.0 | 50.0 | 0.0 | 1450.0 | 250.0 | 0.0 | 0.0 | 1200.0 | + + @TestRailId:C2978 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify that multiple disbursement amount only distributed only to future installments (2nd and 3rd installments) + When Admin sets the business date to "01 September 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" + When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "01 October 2023" + When Admin successfully disburse the loan on "01 October 2023" with "400" EUR transaction amount + When Admin successfully disburse the loan on "01 October 2023" with "80" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | | | 01 October 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 October 2023 | | 80.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 October 2023 | | 880.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 4 | 0 | 01 October 2023 | | 860.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + | 5 | 31 | 01 November 2023 | | 430.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | + | 6 | 30 | 01 December 2023 | | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1480.0 | 0.0 | 50.0 | 0.0 | 1530.0 | 250.0 | 0.0 | 0.0 | 1280.0 | + + @TestRailId:C2986 @AdvancedPaymentAllocation + Scenario: As an admin I would like to verify that refund is working with advanced payment allocation + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount + Then Loan status has changed to "Active" + When Admin sets the business date to "11 January 2023" + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 January 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "21 January 2023" due date and 20 EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 February 2023" due date and 20 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 60.0 | 0 | 560.0 | 125.0 | 0 | 0 | 435.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + When Admin sets the business date to "16 January 2023" + And Customer makes "AUTOPAY" repayment on "16 January 2023" with 315 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 145.0 | 0.0 | 0.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 25.0 | 25.0 | 0.0 | 120.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 60.0 | 0 | 560.0 | 440.0 | 170 | 0 | 120.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 16 January 2023 | Repayment | 315.0 | 255.0 | 0.0 | 60.0 | 0.0 | + When Admin sets the business date to "17 January 2023" + And Admin makes "REFUND_BY_CASH" transaction with "AUTOPAY" payment type on "17 January 2023" with 150 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 20.0 | 20.0 | 0.0 | 125.0 | + | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 0 | 60.0 | 0 | 560.0 | 290.0 | 20 | 0 | 270.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | + | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 16 January 2023 | Repayment | 315.0 | 255.0 | 0.0 | 60.0 | 0.0 | + | 17 January 2023 | Refund | 150.0 | 130.0 | 0.0 | 20.0 | 0.0 | + + @TestRailId:C3042 @AdvancedPaymentAllocation + Scenario: Verify that second disbursement is working on overpaid accounts in case of NEXT_INSTALLMENT future installment allocation rule + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "500" EUR transaction amount + When Admin sets the business date to "16 January 2024" + And Customer makes "AUTOPAY" repayment on "16 January 2024" with 500 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 125 overpaid amount + When Admin successfully disburse the loan on "16 January 2024" with "500" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 375 outstanding amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 | + | 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2024 | Repayment | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 16 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3043 @AdvancedPaymentAllocation + Scenario: Verify that second disbursement is working on overpaid accounts in case of REAMORTIZATION future installment allocation rule + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "500" EUR transaction amount + When Admin sets the business date to "16 January 2024" + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "16 January 2024" with 500 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "OVERPAID" + Then Loan has 125 overpaid amount + When Admin successfully disburse the loan on "16 January 2024" with "500" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 375 outstanding amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 | + | 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 16 January 2024 | Payout Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 16 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 375.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3046 @AdvancedPaymentAllocation + Scenario: Verify that principal due is correct in case of second disbursement on overpaid loan + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "200" EUR transaction amount + When Admin sets the business date to "02 February 2024" + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "02 February 2024" with 200 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "OVERPAID" + Then Loan has 50 overpaid amount + When Admin successfully disburse the loan on "02 February 2024" with "160" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | 01 February 2024 | 150.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | + | | | 02 February 2024 | | 160.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 02 February 2024 | 02 February 2024 | 270.0 | 40.0 | 0.0 | 0.0 | 0.0 | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 16 February 2024 | | 180.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.33 | 53.33 | 0.0 | 36.67 | + | 4 | 15 | 02 March 2024 | | 90.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.33 | 53.33 | 0.0 | 36.67 | + | 5 | 15 | 17 March 2024 | | 0.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.34 | 53.34 | 0.0 | 36.66 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 360.0 | 0 | 0.0 | 0 | 360.0 | 250.0 | 160.0 | 0 | 110.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 01 February 2024 | Down Payment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 02 February 2024 | Payout Refund | 200.0 | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 02 February 2024 | Disbursement | 160.0 | 0.0 | 0.0 | 0.0 | 0.0 | 110.0 | + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3049 @AdvancedPaymentAllocation + Scenario: Verify that second disbursement on overpaid loan is correct when disbursement amount is lower than overpayment amount + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "200" EUR transaction amount + When Admin sets the business date to "02 February 2024" + When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "02 February 2024" with 200 EUR transaction amount and system-generated Idempotency key + Then Loan status will be "OVERPAID" + Then Loan has 50 overpaid amount + When Admin successfully disburse the loan on "02 February 2024" with "28" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | 01 February 2024 | 150.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | + | | | 02 February 2024 | | 28.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 02 February 2024 | 02 February 2024 | 171.0 | 7.0 | 0.0 | 0.0 | 0.0 | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | + | 3 | 15 | 16 February 2024 | 02 February 2024 | 114.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | + | 4 | 15 | 02 March 2024 | 02 February 2024 | 57.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | + | 5 | 15 | 17 March 2024 | 02 February 2024 | 0.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 228.0 | 0 | 0.0 | 0 | 228.0 | 228.0 | 171.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 01 February 2024 | Down Payment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 02 February 2024 | Payout Refund | 200.0 | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 02 February 2024 | Disbursement | 28.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status will be "OVERPAID" + Then Loan has 22 overpaid amount + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3068 @AdvancedPaymentAllocation + Scenario: Verify that Fraud flag can be applied on loan is its every status + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + Then Loan status will be "SUBMITTED_AND_PENDING_APPROVAL" + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + Then Loan status will be "APPROVED" + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + When Admin sets the business date to "02 February 2024" + And Customer makes "AUTOPAY" repayment on "02 February 2024" with 100 EUR transaction amount + Then Loan status will be "ACTIVE" + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + When Admin sets the business date to "03 February 2024" + And Customer makes "AUTOPAY" repayment on "03 February 2024" with 750 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 100 overpaid amount + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + When Admin sets the business date to "04 February 2024" + When Customer undo "1"th "Repayment" transaction made on "02 February 2024" + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Admin can successfully set Fraud flag to the loan + Then Admin can successfully unset Fraud flag to the loan + + @TestRailId:C3090 + Scenario: Verify that disbursement can be done on overpaid loan in case of cummulative loan schedule + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO | 01 February 2024 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + When Admin sets the business date to "02 February 2024" + And Customer makes "AUTOPAY" repayment on "02 February 2024" with 800 EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 50 overpaid amount + When Admin sets the business date to "03 February 2024" + When Admin successfully disburse the loan on "03 February 2024" with "20" EUR transaction amount + Then Loan status will be "OVERPAID" + Then Loan has 30 overpaid amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | + | 3 | 29 | 01 March 2024 | 02 February 2024 | 510.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | + | 4 | 31 | 01 April 2024 | 02 February 2024 | 255.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | + | 5 | 30 | 01 May 2024 | 02 February 2024 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1020.0 | 0 | 0.0 | 0 | 1020.0 | 1020.0 | 770.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 02 February 2024 | Repayment | 800.0 | 770.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + When Admin sets the business date to "04 February 2024" + When Admin successfully disburse the loan on "04 February 2024" with "30" EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | + | | | 04 February 2024 | | 30.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 04 February 2024 | 02 February 2024 | 787.0 | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 8.0 | 8.0 | 0.0 | 0.0 | + | 4 | 29 | 01 March 2024 | 02 February 2024 | 524.0 | 263.0 | 0.0 | 0.0 | 0.0 | 263.0 | 263.0 | 263.0 | 0.0 | 0.0 | + | 5 | 31 | 01 April 2024 | 02 February 2024 | 261.0 | 263.0 | 0.0 | 0.0 | 0.0 | 263.0 | 263.0 | 263.0 | 0.0 | 0.0 | + | 6 | 30 | 01 May 2024 | 02 February 2024 | 0.0 | 261.0 | 0.0 | 0.0 | 0.0 | 261.0 | 261.0 | 261.0 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1050.0 | 0 | 0.0 | 0 | 1050.0 | 1050.0 | 800.0 | 0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 02 February 2024 | Repayment | 800.0 | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 04 February 2024 | Disbursement | 30.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + When Admin sets the business date to "05 February 2024" + When Admin successfully disburse the loan on "05 February 2024" with "40" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has 30 outstanding amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | + | | | 04 February 2024 | | 30.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 04 February 2024 | 02 February 2024 | 787.0 | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 8.0 | 8.0 | 0.0 | 0.0 | + | | | 05 February 2024 | | 40.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 4 | 0 | 05 February 2024 | 02 February 2024 | 817.0 | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 10.0 | 10.0 | 0.0 | 0.0 | + | 5 | 29 | 01 March 2024 | 02 February 2024 | 544.0 | 273.0 | 0.0 | 0.0 | 0.0 | 273.0 | 273.0 | 273.0 | 0.0 | 0.0 | + | 6 | 31 | 01 April 2024 | 02 February 2024 | 271.0 | 273.0 | 0.0 | 0.0 | 0.0 | 273.0 | 273.0 | 273.0 | 0.0 | 0.0 | + | 7 | 30 | 01 May 2024 | | 0.0 | 271.0 | 0.0 | 0.0 | 0.0 | 271.0 | 241.0 | 241.0 | 0.0 | 30.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1090.0 | 0 | 0.0 | 0 | 1090.0 | 1060.0 | 810.0 | 0 | 30.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | + | 02 February 2024 | Repayment | 800.0 | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 04 February 2024 | Disbursement | 30.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 05 February 2024 | Disbursement | 40.0 | 0.0 | 0.0 | 0.0 | 0.0 | 40.0 | + | 05 February 2024 | Down Payment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 30.0 | + + @TestRailId:C3103 + Scenario: Verify that fixed length in loan product is inherited by loan account + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 90 + + @TestRailId:C3104 + Scenario: Verify that fixed length in loan product can be overwrote upon loan account creation + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with fixed length 60 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 60 + + @TestRailId:C3119 + Scenario: Verify fixed length loan account Loan schedule - UC1: loan account with fixed length of 40 days (loanTermFrequency: 45 days) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 40 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3120 + Scenario: Verify fixed length loan account Loan schedule - UC2: loan account with fixed length of 50 days (loanTermFrequency: 45 days) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 50 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 50 + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 20 | 22 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3121 + Scenario: Verify fixed length loan account Loan schedule - UC3: loan account with fixed length of 5 weeks (loanTermFrequency: 6 weeks) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 5 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | WEEKS | 2 | WEEKS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 5 + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 14 | 15 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 14 | 29 February 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 7 | 07 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3122 + Scenario: Verify fixed length loan account Loan schedule - UC4: loan account with fixed length of 7 weeks (loanTermFrequency: 6 weeks) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 7 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | WEEKS | 2 | WEEKS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 7 + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 14 | 15 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 14 | 29 February 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 21 | 21 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3123 + Scenario: Verify fixed length loan account Loan schedule - UC5: loan account with fixed length of 5 months (loanTermFrequency: 6 months) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 5 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 2 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 5 + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 60 | 01 April 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 61 | 01 June 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 30 | 01 July 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3124 + Scenario: Verify fixed length loan account Loan schedule - UC6: loan account with fixed length of 7 months (loanTermFrequency: 6 months) + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 7 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 2 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then LoanDetails has fixedLength field with int value: 7 + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 60 | 01 April 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 61 | 01 June 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 92 | 01 September 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3125 + Scenario: Verify fixed length loan account Loan schedule - UC7: loan account with fixed length of 5 months but 6 month period / repayment in every 1 month results an ERROR + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Trying to create a fully customized loan with fixed length 5 and with the following data will result a 403 ERROR: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + + @TestRailId:C3126 + Scenario: Verify fixed length loan account Loan schedule - UC8: Reschedule by date a loan account with fixed length of 40 days + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 40 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "02 March 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 02 March 2024 | 02 March 2024 | 20 March 2024 | | | | | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 33 | 20 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 10 | 30 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3127 + Scenario: Verify fixed length loan account Loan schedule - UC9: Reschedule by extra terms a loan account with fixed length of 40 days + When Admin sets the business date to "01 February 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with fixed length 40 and with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" + When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "16 February 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 16 February 2024 | 16 February 2024 | | | | 2 | | + # Verify Progressive Loan reschedule behavior - future installments recalculated + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 15 | 16 February 2024 | | 600.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 3 | 15 | 02 March 2024 | | 450.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 4 | 10 | 12 March 2024 | | 300.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 5 | 20 | 01 April 2024 | | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 6 | 15 | 16 April 2024 | | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3192 + Scenario: Verify that the error message is correct in case of the actual disbursement date is in the past with advanced payment allocation product + submitted on date repaymentStartDateType + When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "SUBMITTED_ON_DATE" + When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin sets the business date to "01 January 2023" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" + Then Loan status has changed to "Approved" + Then Admin fails to disburse the loan on "31 December 2022" with "500" EUR transaction amount because disbursement date is earlier than "01 January 2023" + When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "DISBURSEMENT_DATE" + + @TestRailId:C3242 + Scenario: Verify that there are no restriction on repayment reversal for overpaid loan for interest bearing product + When Admin sets the business date to "23 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 23 Jun 2024 | 400 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "23 June 2024" with "400" amount and expected disbursement date on "23 June 2024" + When Admin successfully disburse the loan on "23 June 2024" with "400" EUR transaction amount + And Admin sets the business date to "24 June 2024" + And Customer makes "AUTOPAY" repayment on "24 June 2024" with 100 EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 23 July 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 100.0 | 100.0 | 0.0 | 300.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 100.0 | 100.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + And Admin sets the business date to "10 September 2024" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "10 September 2024" with 400 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 10 September 2024 | Merchant Issued Refund | 400.0 | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | + Then Loan status will be "OVERPAID" + When Admin makes Credit Balance Refund transaction on "10 September 2024" with 91.21 EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 10 September 2024 | Merchant Issued Refund | 400.0 | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 10 September 2024 | Credit Balance Refund | 91.21 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + When Customer undo "1"th repayment on "24 June 2024" + Then Loan Repayment schedule has 2 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 400.0 | 0.0 | + | 2 | 49 | 10 September 2024 | | 0.0 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 491.21 | 0.0 | 0.0 | 0.0 | 491.21 | 400.0 | 0.0 | 400.0 | 91.21 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 10 September 2024 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 10 September 2024 | Credit Balance Refund | 91.21 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | + Then In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted + + @TestRailId:C3282 + Scenario: Verify reversal of related interest refund transaction after merchant issued refund transactions is reversed + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount + When Admin sets the business date to "22 January 2024" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + When Customer undo "1"th "Merchant Issued Refund" transaction made on "22 January 2024" with linked "Interest Refund" transaction + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 753.25 | 246.75 | 8.39 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 2 | 29 | 01 March 2024 | | 504.02 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 253.11 | 250.91 | 4.23 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 253.11 | 2.05 | 0.0 | 0.0 | 255.16 | 0.0 | 0.0 | 0.0 | 255.16 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.58 | 0.0 | 0.0 | 1020.58 | 0.0 | 0.0 | 0.0 | 1020.58 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | true | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | true | false | + Then In Loan Transactions the "3"th Transaction has Transaction type="Interest Refund" and is reverted + + @TestRailId:C3283 + Scenario: Verify reversal of related interest refund transaction after payout refund transactions is reversed + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount + When Admin sets the business date to "22 January 2024" + When Admin makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + When Customer undo "1"th "Payout Refund" transaction made on "22 January 2024" with linked "Interest Refund" transaction + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 753.25 | 246.75 | 8.39 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 2 | 29 | 01 March 2024 | | 504.02 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 253.11 | 250.91 | 4.23 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 253.11 | 2.05 | 0.0 | 0.0 | 255.16 | 0.0 | 0.0 | 0.0 | 255.16 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.58 | 0.0 | 0.0 | 1020.58 | 0.0 | 0.0 | 0.0 | 1020.58 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | true | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | true | false | + Then In Loan Transactions the "3"th Transaction has Transaction type="Interest Refund" and is reverted + + @TestRailId:C3324 + Scenario: Verify interest refund transaction after merchant issued refund transactions is forbidden to be reversed + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount + When Admin sets the business date to "22 January 2024" + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + When Customer is forbidden to undo "1"th "Interest Refund" transaction made on "22 January 2024" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + + @TestRailId:C3325 + Scenario: Verify interest refund transaction after payout refund transactions is forbidden to be reversed + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount + When Admin sets the business date to "22 January 2024" + When Admin makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + When Customer is forbidden to undo "1"th "Interest Refund" transaction made on "22 January 2024" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | + | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | + + @TestRailId:C3300 + Scenario: Early pay-off loan with interest, TILL_PRECLOSE product + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | + When Admin sets the business date to "15 February 2024" + When Loan Pay-off is made on "15 February 2024" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 February 2024 | 66.8 | 16.77 | 0.24 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 15 February 2024 | 49.79 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 15 February 2024 | 32.78 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2024 | 15 February 2024 | 15.77 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2024 | 15 February 2024 | 0.0 | 15.77 | 0.0 | 0.0 | 0.0 | 15.77 | 15.77 | 15.77 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | + | 15 February 2024 | Repayment | 83.81 | 83.57 | 0.24 | 0.0 | 0.0 | 0.0 | false | + | 15 February 2024 | Accrual | 0.82 | 0.0 | 0.82 | 0.0 | 0.0 | 0.0 | false | + Then Loan's all installments have obligations met + + @TestRailId:C3483 + Scenario: Early pay-off loan with interest, TILL_REST_FREQUENCY_DATE product + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | + When Admin sets the business date to "15 February 2024" + When Loan Pay-off is made on "15 February 2024" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 February 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 15 February 2024 | 50.04 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 15 February 2024 | 33.03 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2024 | 15 February 2024 | 16.02 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2024 | 15 February 2024 | 0.0 | 16.02 | 0.0 | 0.0 | 0.0 | 16.02 | 16.02 | 16.02 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | + | 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false | + | 15 February 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | + Then Loan's all installments have obligations met + + @TestRailId:C3484 + Scenario: Interest recalculation - S1 daily for overdue loan + Given Global configuration "enable-business-date" is enabled + When Admin sets the business date to "1 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" + When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount + When Admin sets the business date to "15 July 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.71 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 34.28 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 17.85 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 17.85 | 0.58 | 0.0 | 0.0 | 18.43 | 0.0 | 0.0 | 0.0 | 18.43 | + + @TestRailId:C3485 + Scenario: Interest recalculation - S2 2 overdue + Given Global configuration "enable-business-date" is enabled + When Admin sets the business date to "1 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" + When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount + When Admin sets the business date to "10 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.58 | 16.56 | 0.45 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.87 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 17.06 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 17.06 | 0.1 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 | + + @TestRailId:C3486 + Scenario: Interest recalculation - S3 1 paid, 1 overdue + Given Global configuration "enable-business-date" is enabled + When Admin sets the business date to "1 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" + When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount + When Admin sets the business date to "1 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + When Admin sets the business date to "10 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.46 | 16.59 | 0.42 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.74 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.93 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.93 | 0.1 | 0.0 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | 17.03 | + + @TestRailId:C3487 + Scenario: Loan Details Emi Amount Variations - AssociationsAll + Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled + Given Global configuration "enable-business-date" is enabled + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with emi and the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount and "50" fixed emi amount + Then Loan emi amount variations has 1 variation, with the following data: + | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | + | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | + + @TestRailId:C3488 + Scenario: Loan Details Loan Term Variations + Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled + Given Global configuration "enable-business-date" is enabled + When Admin sets the business date to "1 January 2023" + When Admin creates a client with random data + When Admin creates a fully customized loan with emi and the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount, "50" EUR fixed emi amount and adjust repayment date on "15 January 2023" + Then Loan term variations has 2 variation, with the following data: + | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | + | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | + | 4 | loanTermType.dueDate | dueDate | 01 February 2023 | 50.0 | 15 January 2023 | false | | + + @TestRailId:C3489 + Scenario: EMI calculation with 360/30 Early repayment - Last installment strategy - UC1: Multiple early repayment + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | +# --- Early repayment on 15 January 2024 --- + When Admin sets the business date to "15 January 2024" + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 15.00 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.53 | 16.47 | 0.54 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 66.92 | 16.61 | 0.4 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.21 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.41 | 16.8 | 0.21 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.51 | 16.9 | 0.11 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.51 | 0.01 | 0.0 | 0.0 | 16.52 | 15.0 | 15.0 | 0.0 | 1.52 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.57 | 0.0 | 0.0 | 101.57 | 15.0 | 15.0 | 0.0 | 86.57 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + # --- Early repayment on 15 January 2024 --- + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 1.50 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.52 | 16.48 | 0.53 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 66.9 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.18 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.37 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.5 | 16.87 | 0.1 | 0.0 | 0.0 | 16.97 | 0.0 | 0.0 | 0.0 | 16.97 | + | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.51 | 0.0 | 0.0 | 101.51 | 16.5 | 16.5 | 0.0 | 85.01 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | + # --- Pay-off on 15 January 2024 --- + And Customer makes "AUTOPAY" repayment on "15 January 2024" with 83.76 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 15 January 2024 | 84.54 | 15.46 | 0.26 | 0.0 | 0.0 | 15.72 | 15.72 | 15.72 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 January 2024 | 67.53 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 15 January 2024 | 50.52 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 15 January 2024 | 33.51 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2024 | 15 January 2024 | 16.5 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 0.26 | 0.0 | 0.0 | 100.26 | 100.26 | 100.26 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | + | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | + | 15 January 2024 | Repayment | 83.76 | 83.5 | 0.26 | 0.0 | 0.0 | 0.0 | + | 15 January 2024 | Accrual | 0.26 | 0.0 | 0.26 | 0.0 | 0.0 | 0.0 | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part4.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part4.feature new file mode 100644 index 00000000000..92912ed29e9 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan-Part4.feature @@ -0,0 +1,2845 @@ +@LoanFeature +Feature: Loan - Part4 + + @TestRailId:C3424 + Scenario: Verify that after maturity date with inline COB the outstanding interest is recognized on repayment schedule + When Admin sets the business date to "23 December 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + |LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_ACTUAL | 23 December 2024 | 100 | 4 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "23 December 2024" with "100" amount and expected disbursement date on "23 December 2024" + When Admin successfully disburse the loan on "23 December 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 December 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 23 January 2025 | | 66.78 | 33.22 | 0.34 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | + | 2 | 31 | 23 February 2025 | | 33.45 | 33.33 | 0.23 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | + | 3 | 28 | 23 March 2025 | | 0.0 | 33.45 | 0.1 | 0.0 | 0.0 | 33.55 | 0.0 | 0.0 | 0.0 | 33.55 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 0.67 | 0.0 | 0.0 | 100.67 | 0.0 | 0.0 | 0.0 | 100.67 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 December 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + And Admin sets the business date to "23 March 2025" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 23 December 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 23 January 2025 | | 66.78 | 33.22 | 0.34 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | + | 2 | 31 | 23 February 2025 | | 33.45 | 33.33 | 0.23 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | + | 3 | 28 | 23 March 2025 | | 0.0 | 33.45 | 0.1 | 0.0 | 0.0 | 33.55 | 0.0 | 0.0 | 0.0 | 33.55 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 0.67 | 0.0 | 0.0 | 100.67 | 0.0 | 0.0 | 0.0 | 100.67 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 23 December 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 22 March 2025 | Accrual | 0.67 | 0.0 | 0.67 | 0.0 | 0.0 | 0.0 | + + @TestRailId:C3534 @AdvancedPaymentAllocation + Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, full Merchant issued Refund on the disbursement date, 2nd disbursement on the same date + When Admin sets the business date to "11 March 2025" + And Admin creates a client with random data + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL" loan product "MERCHANT_ISSUED_REFUND" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 11 March 2025 | 1000 | 26 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12 | MONTHS | 1 | MONTHS | 12 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "11 March 2025" with "1000" amount and expected disbursement date on "11 March 2025" + And Admin successfully disburse the loan on "11 March 2025" with "200" EUR transaction amount + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 11 April 2025 | | 185.3 | 14.7 | 4.42 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 2 | 30 | 11 May 2025 | | 170.14 | 15.16 | 3.96 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 3 | 31 | 11 June 2025 | | 154.78 | 15.36 | 3.76 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 4 | 30 | 11 July 2025 | | 138.97 | 15.81 | 3.31 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 5 | 31 | 11 August 2025 | | 122.92 | 16.05 | 3.07 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 6 | 31 | 11 September 2025 | | 106.51 | 16.41 | 2.71 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 7 | 30 | 11 October 2025 | | 89.67 | 16.84 | 2.28 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 8 | 31 | 11 November 2025 | | 72.53 | 17.14 | 1.98 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 9 | 30 | 11 December 2025 | | 54.96 | 17.57 | 1.55 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 10 | 31 | 11 January 2026 | | 37.05 | 17.91 | 1.21 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 11 | 31 | 11 February 2026 | | 18.75 | 18.3 | 0.82 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + | 12 | 28 | 11 March 2026 | | 0.0 | 18.75 | 0.37 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 200.0 | 29.44 | 0.0 | 0 | 229.44 | 0.0 | 0.0 | 0.0 | 229.44 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "11 March 2025" with 200 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 11 April 2025 | 11 March 2025 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 11 May 2025 | 11 March 2025 | 191.2 | 8.8 | 0.0 | 0.0 | 0.0 | 8.8 | 8.8 | 8.8 | 0.0 | 0.0 | + | 3 | 31 | 11 June 2025 | 11 March 2025 | 172.08 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 4 | 30 | 11 July 2025 | 11 March 2025 | 152.96 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 5 | 31 | 11 August 2025 | 11 March 2025 | 133.84 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 6 | 31 | 11 September 2025 | 11 March 2025 | 114.72 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 7 | 30 | 11 October 2025 | 11 March 2025 | 95.6 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 8 | 31 | 11 November 2025 | 11 March 2025 | 76.48 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 9 | 30 | 11 December 2025 | 11 March 2025 | 57.36 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 10 | 31 | 11 January 2026 | 11 March 2025 | 38.24 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 11 | 31 | 11 February 2026 | 11 March 2025 | 19.12 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + | 12 | 28 | 11 March 2026 | 11 March 2025 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 200.0 | 0 | 0.0 | 0 | 200.0 | 200.0 | 200.0 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 11 March 2025 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | + And Admin successfully disburse the loan on "11 March 2025" with "200" EUR transaction amount + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 11 April 2025 | | 366.18 | 33.82 | 4.42 | 0.0 | 0.0 | 38.24 | 0.0 | 0.0 | 0.0 | 38.24 | + | 2 | 30 | 11 May 2025 | | 331.49 | 34.69 | 3.55 | 0.0 | 0.0 | 38.24 | 8.8 | 8.8 | 0.0 | 29.44 | + | 3 | 31 | 11 June 2025 | | 296.35 | 35.14 | 3.1 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 4 | 30 | 11 July 2025 | | 260.77 | 35.58 | 2.66 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 5 | 31 | 11 August 2025 | | 224.91 | 35.86 | 2.38 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 6 | 31 | 11 September 2025 | | 188.68 | 36.23 | 2.01 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 7 | 30 | 11 October 2025 | | 152.02 | 36.66 | 1.58 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 8 | 31 | 11 November 2025 | | 115.03 | 36.99 | 1.25 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 9 | 30 | 11 December 2025 | | 77.61 | 37.42 | 0.82 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 10 | 31 | 11 January 2026 | | 39.82 | 37.79 | 0.45 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | + | 11 | 31 | 11 February 2026 | | 19.12 | 20.7 | 0.03 | 0.0 | 0.0 | 20.73 | 19.12 | 19.12 | 0.0 | 1.61 | + | 12 | 28 | 11 March 2026 | 11 March 2025 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 400.0 | 22.25 | 0.0 | 0 | 422.25 | 200.0 | 200.0 | 0.0 | 222.25 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + | 11 March 2025 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL" loan product "MERCHANT_ISSUED_REFUND" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3570 + Scenario: Verify Loan is fully paid and closed after full Merchant issued refund 1 day after disbursement + When Admin sets the business date to "29 March 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 28 March 2025 | 1383 | 12.23 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 24 | MONTHS | 1 | MONTHS | 24 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "28 March 2025" with "1383" amount and expected disbursement date on "28 March 2025" + When Admin successfully disburse the loan on "28 March 2025" with "1383" EUR transaction amount + Then Loan Repayment schedule has 24 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 March 2025 | | 1383.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 28 April 2025 | | 1331.85 | 51.15 | 14.1 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 2 | 30 | 28 May 2025 | | 1280.17 | 51.68 | 13.57 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 3 | 31 | 28 June 2025 | | 1227.97 | 52.2 | 13.05 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 4 | 30 | 28 July 2025 | | 1175.24 | 52.73 | 12.52 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 5 | 31 | 28 August 2025 | | 1121.97 | 53.27 | 11.98 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 6 | 31 | 28 September 2025 | | 1068.15 | 53.82 | 11.43 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 7 | 30 | 28 October 2025 | | 1013.79 | 54.36 | 10.89 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 8 | 31 | 28 November 2025 | | 958.87 | 54.92 | 10.33 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 9 | 30 | 28 December 2025 | | 903.39 | 55.48 | 9.77 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 10 | 31 | 28 January 2026 | | 847.35 | 56.04 | 9.21 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 11 | 31 | 28 February 2026 | | 790.74 | 56.61 | 8.64 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 12 | 28 | 28 March 2026 | | 733.55 | 57.19 | 8.06 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 13 | 31 | 28 April 2026 | | 675.78 | 57.77 | 7.48 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 14 | 30 | 28 May 2026 | | 617.42 | 58.36 | 6.89 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 15 | 31 | 28 June 2026 | | 558.46 | 58.96 | 6.29 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 16 | 30 | 28 July 2026 | | 498.9 | 59.56 | 5.69 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 17 | 31 | 28 August 2026 | | 438.73 | 60.17 | 5.08 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 18 | 31 | 28 September 2026 | | 377.95 | 60.78 | 4.47 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 19 | 30 | 28 October 2026 | | 316.55 | 61.4 | 3.85 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 20 | 31 | 28 November 2026 | | 254.53 | 62.02 | 3.23 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 21 | 30 | 28 December 2026 | | 191.87 | 62.66 | 2.59 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 22 | 31 | 28 January 2027 | | 128.58 | 63.29 | 1.96 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 23 | 31 | 28 February 2027 | | 64.64 | 63.94 | 1.31 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | + | 24 | 28 | 28 March 2027 | | 0.0 | 64.64 | 0.66 | 0.0 | 0.0 | 65.3 | 0.0 | 0.0 | 0.0 | 65.3 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1383.0 | 183.05 | 0.0 | 0.0 | 1566.05 | 0.0 | 0.0 | 0.0 | 1566.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 March 2025 | Disbursement | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1383.0 | false | false | + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "29 March 2025" with 1383 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 24 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 March 2025 | | 1383.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 28 April 2025 | 29 March 2025 | 1383.0 | 0.0 | 0.45 | 0.0 | 0.0 | 0.45 | 0.45 | 0.45 | 0.0 | 0.0 | + | 2 | 30 | 28 May 2025 | 29 March 2025 | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 28 June 2025 | 29 March 2025 | 1370.25 | 12.75 | 0.0 | 0.0 | 0.0 | 12.75 | 12.75 | 12.75 | 0.0 | 0.0 | + | 4 | 30 | 28 July 2025 | 29 March 2025 | 1305.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 5 | 31 | 28 August 2025 | 29 March 2025 | 1239.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 6 | 31 | 28 September 2025 | 29 March 2025 | 1174.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 7 | 30 | 28 October 2025 | 29 March 2025 | 1109.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 8 | 31 | 28 November 2025 | 29 March 2025 | 1044.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 9 | 30 | 28 December 2025 | 29 March 2025 | 978.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 10 | 31 | 28 January 2026 | 29 March 2025 | 913.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 11 | 31 | 28 February 2026 | 29 March 2025 | 848.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 12 | 28 | 28 March 2026 | 29 March 2025 | 783.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 13 | 31 | 28 April 2026 | 29 March 2025 | 717.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 14 | 30 | 28 May 2026 | 29 March 2025 | 652.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 15 | 31 | 28 June 2026 | 29 March 2025 | 587.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 16 | 30 | 28 July 2026 | 29 March 2025 | 522.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 17 | 31 | 28 August 2026 | 29 March 2025 | 456.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 18 | 31 | 28 September 2026 | 29 March 2025 | 391.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 19 | 30 | 28 October 2026 | 29 March 2025 | 326.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 20 | 31 | 28 November 2026 | 29 March 2025 | 261.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 21 | 30 | 28 December 2026 | 29 March 2025 | 195.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 21 | 30 | 28 December 2026 | 29 March 2025 | 195.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 22 | 31 | 28 January 2027 | 29 March 2025 | 130.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 23 | 31 | 28 February 2027 | 29 March 2025 | 65.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + | 24 | 28 | 28 March 2027 | 29 March 2025 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1383.0 | 0.45 | 0.0 | 0.0 | 1383.45 | 1383.45 | 1383.45 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 March 2025 | Disbursement | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1383.0 | false | false | + | 29 March 2025 | Merchant Issued Refund | 1383.0 | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2025 | Interest Refund | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2025 | Accrual Activity | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2025 | Accrual | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C3584 + Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - No interest, No interest recalculation + When Admin sets the business date to "14 March 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_DP_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 0.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" + When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | + | 3 | 15 | 13 April 2024 | | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | + | 4 | 15 | 28 April 2024 | | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 121.9 | 0.0 | 0.0 | 365.68 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + When Admin sets the business date to "24 March 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | + | 3 | 15 | 13 April 2024 | | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 79.49 | 79.49 | 0.0 | 42.4 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 323.29 | 201.39 | 0.0 | 164.29 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 164.29 outstanding amount + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 487.58 | 365.68 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 121.9 overpaid amount + When Admin sets the business date to "25 March 2024" + When Admin makes Credit Balance Refund transaction on "25 March 2024" with 121.9 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 487.58 | 365.68 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + When Admin sets the business date to "01 April 2024" + When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 121.89 | 121.89 | 0.0 | 117.27 | + | 5 | 15 | 28 April 2024 | | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 121.9 | 121.9 | 0.0 | 117.25 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 565.75 | 365.68 | 0.0 | 234.52 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 312.69 | false | false | + | 01 April 2024 | Down Payment | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 234.52 outstanding amount + When Admin sets the business date to "10 April 2024" + When Loan Pay-off is made on "10 April 2024" + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | 10 April 2024 | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 239.16 | 239.16 | 0.0 | 0.0 | + | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 239.15 | 239.15 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 800.27 | 600.2 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 312.69 | false | false | + | 01 April 2024 | Down Payment | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | + | 10 April 2024 | Repayment | 234.52 | 234.52 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C3585 + Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - 10% interest, No interest recalculation + When Admin sets the business date to "14 March 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_DP_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 10.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" + When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | + | 3 | 15 | 13 April 2024 | | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | + | 4 | 15 | 28 April 2024 | | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 0.0 | 0.0 | 0.0 | 122.9 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 121.9 | 0.0 | 0.0 | 368.68 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + When Admin sets the business date to "24 March 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | + | 3 | 15 | 13 April 2024 | | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 78.49 | 78.49 | 0.0 | 44.4 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 323.29 | 201.39 | 0.0 | 167.29 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 167.29 outstanding amount + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 490.58 | 368.68 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 118.9 overpaid amount + When Admin sets the business date to "25 March 2024" + When Admin makes Credit Balance Refund transaction on "25 March 2024" with 118 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 490.58 | 368.68 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 0.9 overpaid amount + When Admin sets the business date to "01 April 2024" + When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.81 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | | 239.8 | 239.01 | 1.77 | 0.0 | 0.0 | 240.78 | 122.89 | 122.89 | 0.0 | 117.89 | + | 5 | 15 | 28 April 2024 | | 0.0 | 239.8 | 0.98 | 0.0 | 0.0 | 240.78 | 122.9 | 122.9 | 0.0 | 117.88 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 4.25 | 0.0 | 0.0 | 804.52 | 568.75 | 368.68 | 0.0 | 235.77 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 311.79 | false | false | + | 01 April 2024 | Down Payment | 77.27 | 77.27 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 235.77 outstanding amount + When Admin sets the business date to "10 April 2024" + When Loan Pay-off is made on "10 April 2024" + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.81 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | 10 April 2024 | 239.8 | 239.01 | 1.77 | 0.0 | 0.0 | 240.78 | 240.78 | 240.78 | 0.0 | 0.0 | + | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.8 | 0.98 | 0.0 | 0.0 | 240.78 | 240.78 | 240.78 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 4.25 | 0.0 | 0.0 | 804.52 | 804.52 | 604.45 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 311.79 | false | false | + | 01 April 2024 | Down Payment | 77.27 | 77.27 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | + | 10 April 2024 | Repayment | 235.77 | 234.52 | 1.25 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 1.25 | 0.0 | 1.25 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C3586 + Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - 33.33% interest with interest recalculation + When Admin sets the business date to "14 March 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_DP_IR_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 33.33 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" + When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 245.46 | 120.22 | 5.08 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | + | 3 | 15 | 13 April 2024 | | 123.57 | 121.89 | 3.41 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | + | 4 | 15 | 28 April 2024 | | 0.0 | 123.57 | 1.72 | 0.0 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 10.21 | 0.0 | 0.0 | 497.79 | 121.9 | 0.0 | 0.0 | 375.89 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + When Admin sets the business date to "24 March 2024" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | | 244.53 | 121.15 | 4.15 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | + | 3 | 15 | 13 April 2024 | | 125.29 | 119.24 | 0.6 | 0.0 | 0.0 | 119.84 | 76.1 | 76.1 | 0.0 | 43.74 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 4.75 | 0.0 | 0.0 | 492.33 | 323.29 | 201.39 | 0.0 | 169.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 169.04 outstanding amount + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 125.29 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | 125.3 | 125.3 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.39 | 0.0 | 0.0 | 490.97 | 490.97 | 369.07 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 118.51 overpaid amount + When Admin makes Credit Balance Refund transaction on "24 March 2024" with 11.9 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | + | 3 | 15 | 13 April 2024 | 24 March 2024 | 125.29 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | 125.3 | 125.3 | 0.0 | 0.0 | + | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 487.58 | 3.39 | 0.0 | 0.0 | 490.97 | 490.97 | 369.07 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 106.61 overpaid amount + When Admin sets the business date to "01 April 2024" + When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 485.11 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | | 239.25 | 245.86 | 2.29 | 0.0 | 0.0 | 248.15 | 139.52 | 139.52 | 0.0 | 108.63 | + | 5 | 15 | 28 April 2024 | | 0.0 | 239.25 | 1.39 | 0.0 | 0.0 | 240.64 | 139.51 | 139.51 | 0.0 | 101.13 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 7.07 | 0.0 | 0.0 | 807.34 | 597.58 | 397.51 | 0.0 | 209.76 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 206.08 | false | false | + Then Loan status will be "ACTIVE" + Then Loan has 209.76 outstanding amount + When Admin sets the business date to "10 April 2024" + When Loan Pay-off is made on "10 April 2024" + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | + | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | + | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 0 | 01 April 2024 | 01 April 2024 | 485.11 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | + | 4 | 15 | 13 April 2024 | 10 April 2024 | 241.69 | 243.42 | 1.72 | 0.0 | 0.0 | 245.14 | 245.14 | 245.14 | 0.0 | 0.0 | + | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 241.69 | 0.0 | 0.0 | 0.0 | 241.69 | 241.69 | 241.69 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.27 | 5.11 | 0.0 | 0.0 | 805.38 | 805.38 | 605.31 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | + | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | + | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | + | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 206.08 | false | false | + | 10 April 2024 | Repayment | 207.8 | 206.08 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 1.72 | 0.0 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount + + @TestRailId:C3700 + Scenario: Verify repayment schedule and accrual transactions created after penalty is added for paid off loan on maturity date - UC1 + When Admin sets the business date to "08 May 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_ACCRUAL_ACTIVITY | 08 May 2024 | 1000 | 12.19 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "08 May 2024" with "1000" amount and expected disbursement date on "08 May 2024" + When Admin successfully disburse the loan on "08 May 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + | 2 | 30 | 08 July 2024 | | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 0.0 | 0.0 | 0.0 | 1020.39 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "08 June 2024" + And Customer makes "AUTOPAY" repayment on "08 June 2024" with 340.13 EUR transaction amount + When Admin sets the business date to "08 July 2024" + And Customer makes "AUTOPAY" repayment on "08 July 2024" with 340.13 EUR transaction amount + When Admin sets the business date to "08 August 2024" + And Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "08 August 2024" with 340.13 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2024 | 08 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 1020.39 | 0.0 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual Activity | 3.42 | 0.0 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | +# --- add penalty for paid off loan on maturity date ---# + When Admin adds "LOAN_NSF_FEE" due date charge with "08 August 2024" due date and 2.8 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "09 August 2024" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + When Admin sets the business date to "15 August 2024" + And Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "15 August 2024" with 2.8 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2024 | 15 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + | 15 August 2024 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + When Admin sets the business date to "16 August 2024" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2024 | 15 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + | 15 August 2024 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + + @TestRailId:C3701 + Scenario: Verify repayment schedule and accrual transactions created after penalty is added for paid off loan with accrual activity on maturity date - UC2 + When Admin sets the business date to "08 May 2025" + And Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY | 08 May 2025 | 1000 | 12.19 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "08 May 2025" with "1000" amount and expected disbursement date on "08 May 2025" + When Admin successfully disburse the loan on "08 May 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + | 2 | 30 | 08 July 2025 | | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 0.0 | 0.0 | 0.0 | 1020.39 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + When Admin sets the business date to "08 June 2025" + And Customer makes "AUTOPAY" repayment on "08 June 2025" with 340.13 EUR transaction amount + When Admin sets the business date to "08 July 2025" + And Customer makes "AUTOPAY" repayment on "08 July 2025" with 340.13 EUR transaction amount + When Admin sets the business date to "08 August 2025" + And Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "08 August 2025" with 340.13 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2025 | 08 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 1020.39 | 0.0 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual Activity | 3.42 | 0.0 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | +# --- add penalty for paid off loan on maturity date ---# + When Admin adds "LOAN_NSF_FEE" due date charge with "08 August 2025" due date and 2.8 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "09 August 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + When Admin sets the business date to "15 August 2025" + And Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "15 August 2025" with 2.8 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2025 | 15 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + When Admin sets the business date to "16 August 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 08 August 2025 | 15 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | + | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | + | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | + | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | + | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | + | 15 August 2025 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + + + + @TestRailId:C3963 + Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 2 | 31 | 01 August 2024 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 2 | 31 | 01 August 2024 | | 445.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | + | 3 | 31 | 01 September 2024 | | 223.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | + | 4 | 30 | 01 October 2024 | | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3964 + Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 31 | 01 August 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 31 | 01 September 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 3 | 31 | 01 August 2024 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 4 | 31 | 01 September 2024 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + | 5 | 30 | 01 October 2024 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3965 + Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1000.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 2 | 31 | 01 August 2024 | | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 2 | | + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1200.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 2 | 31 | 01 August 2024 | | 900.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 3 | 31 | 01 September 2024 | | 600.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 4 | 30 | 01 October 2024 | | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + | 5 | 31 | 01 November 2024 | | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + + @TestRailId:C3966 + Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + When Admin sets the business date to "01 July 2024" + And Customer makes "AUTOPAY" repayment on "01 July 2024" with 400 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "15 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 15 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 533.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | + | 3 | 31 | 01 September 2024 | | 266.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | + | 4 | 30 | 01 October 2024 | | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + + @TestRailId:C3967 + Scenario: Verify Progressive Loan reschedule by extending repayment periods after partial repayment and then backdated repayment occurs, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + When Admin sets the business date to "01 July 2024" + And Customer makes "AUTOPAY" repayment on "01 July 2024" with 400 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "15 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 September 2024 | 15 July 2024 | | | | 2 | | + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "15 October 2024" + And Customer makes "AUTOPAY" repayment on "01 August 2024" with 500 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | 01 August 2024 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 100.0 | 100.0 | 0.0 | 34.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 900.0 | 100.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + | 01 August 2024 | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3987 + Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, declining balance interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 2 | 31 | 01 August 2024 | | 336.66 | 333.32 | 6.7 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 3 | 31 | 01 September 2024 | | 0.0 | 336.66 | 3.37 | 0.0 | 0.0 | 340.03 | 0.0 | 0.0 | 0.0 | 340.03 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.07 | 0.0 | 0.0 | 1020.07 | 0.0 | 0.0 | 0.0 | 1020.07 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 2 | 31 | 01 August 2024 | | 448.87 | 221.11 | 6.7 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + | 3 | 31 | 01 September 2024 | | 225.55 | 223.32 | 4.49 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + | 4 | 30 | 01 October 2024 | | 0.0 | 225.55 | 2.26 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 23.45 | 0.0 | 0.0 | 1023.45 | 0.0 | 0.0 | 0.0 | 1023.45 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3989 + Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, declining balance interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_INTEREST | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 502.4 | 247.6 | 7.4 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | + | 3 | 31 | 01 August 2024 | | 252.52 | 249.88 | 5.12 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | + | 4 | 31 | 01 September 2024 | | 0.0 | 252.52 | 2.57 | 0.0 | 0.0 | 255.09 | 0.0 | 0.0 | 0.0 | 255.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 15.09 | 0.0 | 0.0 | 1015.09 | 0.0 | 0.0 | 0.0 | 1015.09 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 629.4 | 120.6 | 7.4 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 3 | 31 | 01 August 2024 | | 507.81 | 121.59 | 6.41 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 4 | 31 | 01 September 2024 | | 384.99 | 122.82 | 5.18 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 5 | 30 | 01 October 2024 | | 0.0 | 384.99 | 3.8 | 0.0 | 0.0 | 388.79 | 0.0 | 0.0 | 0.0 | 388.79 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 22.79 | 0.0 | 0.0 | 1022.79 | 0.0 | 0.0 | 0.0 | 1022.79 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3990 + Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, declining balance interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1004.97 | 495.03 | 15.0 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | + | 2 | 31 | 01 August 2024 | | 504.99 | 499.98 | 10.05 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 504.99 | 5.05 | 0.0 | 0.0 | 510.04 | 0.0 | 0.0 | 0.0 | 510.04 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 30.1 | 0.0 | 0.0 | 1530.1 | 0.0 | 0.0 | 0.0 | 1530.1 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 2 | | + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1205.94 | 294.06 | 15.0 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 2 | 31 | 01 August 2024 | | 908.94 | 297.0 | 12.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 3 | 31 | 01 September 2024 | | 608.97 | 299.97 | 9.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 4 | 30 | 01 October 2024 | | 306.0 | 302.97 | 6.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 5 | 31 | 01 November 2024 | | 0.0 | 306.0 | 3.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 45.3 | 0.0 | 0.0 | 1545.3 | 0.0 | 0.0 | 0.0 | 1545.3 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + + @TestRailId:C3994 + Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, declining balance interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | + | 2 | 31 | 01 August 2024 | | 403.98 | 399.99 | 8.04 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 403.98 | 4.04 | 0.0 | 0.0 | 408.02 | 0.0 | 0.0 | 0.0 | 408.02 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 24.08 | 0.0 | 0.0 | 1224.08 | 0.0 | 0.0 | 0.0 | 1224.08 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + When Admin sets the business date to "01 July 2024" + And Customer makes "AUTOPAY" repayment on "01 July 2024" with 417.03 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 403.89 | 400.08 | 7.95 | 0.0 | 0.0 | 408.03 | 9.0 | 9.0 | 0.0 | 399.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 403.89 | 4.04 | 0.0 | 0.0 | 407.93 | 0.0 | 0.0 | 0.0 | 407.93 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 23.99 | 0.0 | 0.0 | 1223.99 | 417.03 | 9.0 | 0.0 | 806.96 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | + When Admin sets the business date to "15 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 15 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 538.58 | 265.39 | 7.95 | 0.0 | 0.0 | 273.34 | 9.0 | 9.0 | 0.0 | 264.34 | + | 3 | 31 | 01 September 2024 | | 270.63 | 267.95 | 5.39 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | + | 4 | 30 | 01 October 2024 | | 0.0 | 270.63 | 2.71 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 28.05 | 0.0 | 0.0 | 1228.05 | 417.03 | 9.0 | 0.0 | 811.02 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | + + @TestRailId:C4028 + Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with exact disb amount in expected order - UC1 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 200.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 700.0 | | + | 01 January 2025 | | 200.0 | | +# --- 1st disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | | 200.0 | | +# --- 2nd disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | 01 January 2025 | 200.0 | | + + When Loan Pay-off is made on "01 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4029 + Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with over expected disb amount in expected order - UC2 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 200.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 700.0 | | + | 01 January 2025 | | 200.0 | | +# --- 1st disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "750" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 626.81 | 123.19 | 4.37 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | + | 2 | 28 | 01 March 2025 | | 502.91 | 123.9 | 3.66 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | + | 3 | 31 | 01 April 2025 | | 378.28 | 124.63 | 2.93 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | + | 4 | 30 | 01 May 2025 | | 252.93 | 125.35 | 2.21 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | + | 5 | 31 | 01 June 2025 | | 126.85 | 126.08 | 1.48 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | + | 6 | 30 | 01 July 2025 | | 0.0 | 126.85 | 0.74 | 0.0 | 0.0 | 127.59 | 0.0 | 0.0 | 0.0 | 127.59 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 750.0 | 15.39 | 0.0 | 0.0 | 765.39 | 0.0 | 0.0 | 0.0 | 765.39 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 750.0 | | + | 01 January 2025 | | 200.0 | | +# --- 2nd disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "250" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 2 | 28 | 01 March 2025 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 3 | 31 | 01 April 2025 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 4 | 30 | 01 May 2025 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 5 | 31 | 01 June 2025 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 6 | 30 | 01 July 2025 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 01 January 2025 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 750.0 | | + | 01 January 2025 | 01 January 2025 | 250.0 | | + + When Loan Pay-off is made on "1 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4030 + Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with over expected disb amount in not expected order - UC3 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 200.0 | | + | 01 January 2025 | | 500.0 | | +# --- 1st disbursement - 1 January, 2025 --- + Then Admin fails to disburse the loan on "1 January 2025" with "801" EUR transaction amount due to exceed approved amount + When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 250.72 | 49.28 | 1.75 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | + | 2 | 28 | 01 March 2025 | | 201.15 | 49.57 | 1.46 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | + | 3 | 31 | 01 April 2025 | | 151.29 | 49.86 | 1.17 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | + | 4 | 30 | 01 May 2025 | | 101.14 | 50.15 | 0.88 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | + | 5 | 31 | 01 June 2025 | | 50.7 | 50.44 | 0.59 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | + | 6 | 30 | 01 July 2025 | | 0.0 | 50.7 | 0.3 | 0.0 | 0.0 | 51.0 | 0.0 | 0.0 | 0.0 | 51.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 300.0 | 6.15 | 0.0 | 0.0 | 306.15 | 0.0 | 0.0 | 0.0 | 306.15 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 300.0 | | + | 01 January 2025 | | 200.0 | | +# --- 2nd disbursement - 1 January, 2025 --- + Then Admin fails to disburse the loan on "1 January 2025" with "701" EUR transaction amount due to exceed approved amount + When Admin successfully disburse the loan on "01 January 2025" with "600" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 600.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + | 01 January 2025 | Disbursement | 600.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 300.0 | | + | 01 January 2025 | 01 January 2025 | 600.0 | | + + When Loan Pay-off is made on "01 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4031 + Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with diff expected disb amounts in diff order - UC4 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 700.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 200.0 | | + | 01 January 2025 | | 700.0 | | +# --- 1st disbursement - 1 January, 2025 --- + Then Admin fails to disburse the loan on "1 January 2025" with "900" EUR transaction amount due to exceed approved amount + When Admin successfully disburse the loan on "01 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 417.88 | 82.12 | 2.92 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | + | 2 | 28 | 01 March 2025 | | 335.28 | 82.6 | 2.44 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | + | 3 | 31 | 01 April 2025 | | 252.2 | 83.08 | 1.96 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | + | 4 | 30 | 01 May 2025 | | 168.63 | 83.57 | 1.47 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | + | 5 | 31 | 01 June 2025 | | 84.57 | 84.06 | 0.98 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | + | 6 | 30 | 01 July 2025 | | 0.0 | 84.57 | 0.49 | 0.0 | 0.0 | 85.06 | 0.0 | 0.0 | 0.0 | 85.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 500.0 | 10.26 | 0.0 | 0.0 | 510.26 | 0.0 | 0.0 | 0.0 | 510.26 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 500.0 | | + | 01 January 2025 | | 200.0 | | +# --- 2nd disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 668.6 | 131.4 | 4.67 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | + | 2 | 28 | 01 March 2025 | | 536.43 | 132.17 | 3.9 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | + | 3 | 31 | 01 April 2025 | | 403.49 | 132.94 | 3.13 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | + | 4 | 30 | 01 May 2025 | | 269.77 | 133.72 | 2.35 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | + | 5 | 31 | 01 June 2025 | | 135.27 | 134.5 | 1.57 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | + | 6 | 30 | 01 July 2025 | | 0.0 | 135.27 | 0.79 | 0.0 | 0.0 | 136.06 | 0.0 | 0.0 | 0.0 | 136.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 16.41 | 0.0 | 0.0 | 816.41 | 0.0 | 0.0 | 0.0 | 816.41 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 500.0 | | + | 01 January 2025 | 01 January 2025 | 300.0 | | + + When Loan Pay-off is made on "1 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4032 + Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date in defined order with over expected 2nd disb amount - UC5 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 200.0 | | + | 01 January 2025 | | 500.0 | | +# --- 1st disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 167.15 | 32.85 | 1.17 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | + | 2 | 28 | 01 March 2025 | | 134.11 | 33.04 | 0.98 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | + | 3 | 31 | 01 April 2025 | | 100.87 | 33.24 | 0.78 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | + | 4 | 30 | 01 May 2025 | | 67.44 | 33.43 | 0.59 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | + | 5 | 31 | 01 June 2025 | | 33.81 | 33.63 | 0.39 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | + | 6 | 30 | 01 July 2025 | | 0.0 | 33.81 | 0.2 | 0.0 | 0.0 | 34.01 | 0.0 | 0.0 | 0.0 | 34.01 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 200.0 | 4.11 | 0.0 | 0.0 | 204.11 | 0.0 | 0.0 | 0.0 | 204.11 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 200.0 | | + | 01 January 2025 | | 500.0 | | +# --- 2nd disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "800" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 2 | 28 | 01 March 2025 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 3 | 31 | 01 April 2025 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 4 | 30 | 01 May 2025 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 5 | 31 | 01 June 2025 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 6 | 30 | 01 July 2025 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | false | + | 01 January 2025 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 200.0 | | + | 01 January 2025 | 01 January 2025 | 800.0 | | + + When Loan Pay-off is made on "1 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4033 + Scenario: Verify tranche interest bearing progressive loan that expects tranche with added 2nd tranche at the same date and undo disbursement - UC6 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + And Admin successfully add disbursement detail to the loan on "01 January 2025" with 300 EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | | 300.0 | 700.0 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | +# --- 2nd disbursement - 1 Jan, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | 01 January 2025 | 200.0 | 700.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | + Then Admin fails to disburse the loan on "01 January 2025" with "100" amount +# -- undo disbursement ---- + When Admin successfully undo disbursal + Then Loan status has changed to "Approved" + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 700.0 | | + | 01 January 2025 | | 200.0 | 700.0 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | + | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | + Then Loan Transactions tab has none transaction +#---- make two disbursements on Jan1 , 2025 ---# + When Admin successfully disburse the loan on "01 January 2025" with "750" EUR transaction amount + When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 750.0 | | + | 01 January 2025 | 01 January 2025 | 200.0 | 700.0 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 793.96 | 156.04 | 5.54 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | + | 2 | 28 | 01 March 2025 | | 637.01 | 156.95 | 4.63 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | + | 3 | 31 | 01 April 2025 | | 479.15 | 157.86 | 3.72 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | + | 4 | 30 | 01 May 2025 | | 320.37 | 158.78 | 2.8 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | + | 5 | 31 | 01 June 2025 | | 160.66 | 159.71 | 1.87 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | + | 6 | 30 | 01 July 2025 | | 0.0 | 160.66 | 0.94 | 0.0 | 0.0 | 161.6 | 0.0 | 0.0 | 0.0 | 161.6 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 950.0 | 19.5 | 0.0 | 0.0 | 969.5 | 0.0 | 0.0 | 0.0 | 969.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 950.0 | false | false | + When Admin sets the business date to "01 February 2025" + When Admin runs inline COB job for Loan + Then Admin fails to disburse the loan on "01 February 2025" with "50" amount + + When Loan Pay-off is made on "1 February 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4034 + Scenario: Verify tranche interest bearing progressive loan that expects tranches at the same date with repayment and undo last disbursement - UC7 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 90 | DAYS | 15 | DAYS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 300.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 15 | 16 January 2025 | | 834.55 | 165.45 | 2.92 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 2 | 15 | 31 January 2025 | | 668.61 | 165.94 | 2.43 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 3 | 15 | 15 February 2025 | | 502.19 | 166.42 | 1.95 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 4 | 15 | 02 March 2025 | | 335.28 | 166.91 | 1.46 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 5 | 15 | 17 March 2025 | | 167.89 | 167.39 | 0.98 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 6 | 15 | 01 April 2025 | | 0.0 | 167.89 | 0.49 | 0.0 | 0.0 | 168.38 | 0.0 | 0.0 | 0.0 | 168.38 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 10.23 | 0.0 | 0.0 | 1010.23 | 0.0 | 0.0 | 0.0 | 1010.23 | +# --- 1st disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | | 300.0 | | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | | 584.18 | 115.82 | 2.04 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 2 | 15 | 31 January 2025 | | 468.02 | 116.16 | 1.7 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 3 | 15 | 15 February 2025 | | 351.53 | 116.49 | 1.37 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 4 | 15 | 02 March 2025 | | 234.7 | 116.83 | 1.03 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 5 | 15 | 17 March 2025 | | 117.52 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 6 | 15 | 01 April 2025 | | 0.0 | 117.52 | 0.34 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 7.16 | 0.0 | 0.0 | 707.16 | 0.0 | 0.0 | 0.0 | 707.16 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | +# --- 1st repayment - 1 January, 2025 --- + And Customer makes "AUTOPAY" repayment on "01 January 2025" with 117.86 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | 01 January 2025 | 582.14 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | 117.86 | 117.86 | 0.0 | 0.0 | + | 2 | 15 | 31 January 2025 | | 467.68 | 114.46 | 3.4 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 3 | 15 | 15 February 2025 | | 351.18 | 116.5 | 1.36 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 4 | 15 | 02 March 2025 | | 234.34 | 116.84 | 1.02 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 5 | 15 | 17 March 2025 | | 117.16 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 6 | 15 | 01 April 2025 | | 0.0 | 117.16 | 0.34 | 0.0 | 0.0 | 117.5 | 0.0 | 0.0 | 0.0 | 117.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 6.8 | 0.0 | 0.0 | 706.8 | 117.86 | 117.86 | 0.0 | 588.94 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | +# --- 2nd disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + | 01 January 2025 | 01 January 2025 | 300.0 | | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | | 834.2 | 165.8 | 2.57 | 0.0 | 0.0 | 168.37 | 117.86 | 117.86 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 668.26 | 165.94 | 2.43 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 3 | 15 | 15 February 2025 | | 501.84 | 166.42 | 1.95 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 4 | 15 | 02 March 2025 | | 334.93 | 166.91 | 1.46 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 5 | 15 | 17 March 2025 | | 167.54 | 167.39 | 0.98 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | + | 6 | 15 | 01 April 2025 | | 0.0 | 167.54 | 0.49 | 0.0 | 0.0 | 168.03 | 0.0 | 0.0 | 0.0 | 168.03 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 9.88 | 0.0 | 0.0 | 1009.88 | 117.86 | 117.86 | 0.0 | 892.02 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 882.14 | false | false | + Then Admin fails to disburse the loan on "01 January 2025" with "100" amount due to exceed approved amount +# --- undo disbursement --- # + When Admin successfully undo last disbursal + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 700.0 | | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | 01 January 2025 | 582.14 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | 117.86 | 117.86 | 0.0 | 0.0 | + | 2 | 15 | 31 January 2025 | | 467.68 | 114.46 | 3.4 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 3 | 15 | 15 February 2025 | | 351.18 | 116.5 | 1.36 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 4 | 15 | 02 March 2025 | | 234.34 | 116.84 | 1.02 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 5 | 15 | 17 March 2025 | | 117.16 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | + | 6 | 15 | 01 April 2025 | | 0.0 | 117.16 | 0.34 | 0.0 | 0.0 | 117.5 | 0.0 | 0.0 | 0.0 | 117.5 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 700.0 | 6.8 | 0.0 | 0.0 | 706.8 | 117.86 | 117.86 | 0.0 | 588.94 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | + | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | + Then Admin fails to disburse the loan on "01 January 2025" with "200" amount + + When Loan Pay-off is made on "1 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4035 + Scenario: Verify tranche interest bearing progressive loan that expects tranche with added 2 tranches at the same date - UC8 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 90 | DAYS | 15 | DAYS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 300.0 | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 3 | 15 | 15 February 2025 | | 150.67 | 49.92 | 0.59 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 4 | 15 | 02 March 2025 | | 100.6 | 50.07 | 0.44 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 5 | 15 | 17 March 2025 | | 50.38 | 50.22 | 0.29 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 6 | 15 | 01 April 2025 | | 0.0 | 50.38 | 0.15 | 0.0 | 0.0 | 50.53 | 0.0 | 0.0 | 0.0 | 50.53 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 300.0 | 3.08 | 0.0 | 0.0 | 303.08 | 0.0 | 0.0 | 0.0 | 303.08 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 300.0 | | + And Admin successfully add disbursement detail to the loan on "01 February 2025" with 500 EUR transaction amount + And Admin successfully add disbursement detail to the loan on "01 February 2025" with 200 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 3 | 15 | 15 February 2025 | | 676.32 | 224.27 | 2.49 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | + | 4 | 15 | 02 March 2025 | | 451.53 | 224.79 | 1.97 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | + | 5 | 15 | 17 March 2025 | | 226.09 | 225.44 | 1.32 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | + | 6 | 15 | 01 April 2025 | | 0.0 | 226.09 | 0.66 | 0.0 | 0.0 | 226.75 | 0.0 | 0.0 | 0.0 | 226.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.00 | 8.05 | 0.0 | 0.0 | 1008.05 | 0.0 | 0.0 | 0.0 | 1008.05 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | | 300.0 | | + | 01 February 2025 | | 500.0 | 1000.0 | + | 01 February 2025 | | 200.0 | 1000.0 | +# --- 1st disbursement - 1 January, 2025 --- + When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 3 | 15 | 15 February 2025 | | 850.67 | 49.92 | 0.59 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 4 | 15 | 02 March 2025 | | 800.6 | 50.07 | 0.44 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 5 | 15 | 17 March 2025 | | 750.38 | 50.22 | 0.29 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 6 | 15 | 01 April 2025 | | 700.0 | 50.38 | 0.15 | 0.0 | 0.0 | 50.53 | 0.0 | 0.0 | 0.0 | 50.53 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 300.0 | 3.08 | 0.0 | 0.0 | 303.08 | 0.0 | 0.0 | 0.0 | 303.08 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 300.0 | | + | 01 February 2025 | | 500.0 | 1000.0 | + | 01 February 2025 | | 200.0 | 1000.0 | +# --- 2nd disbursement - 1 February, 2025 --- + When Admin sets the business date to "01 February 2025" + When Admin runs inline COB job for Loan + When Admin successfully disburse the loan on "01 February 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 200.74 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 15 | 15 February 2025 | | 526.31 | 174.43 | 1.97 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | + | 4 | 15 | 02 March 2025 | | 351.45 | 174.86 | 1.54 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | + | 5 | 15 | 17 March 2025 | | 176.08 | 175.37 | 1.03 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | + | 6 | 15 | 01 April 2025 | | 0.0 | 176.08 | 0.51 | 0.0 | 0.0 | 176.59 | 0.0 | 0.0 | 0.0 | 176.59 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 800.0 | 6.81 | 0.0 | 0.0 | 806.81 | 0.0 | 0.0 | 0.0 | 806.81 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 300.0 | | + | 01 February 2025 | 01 February 2025 | 500.0 | 1000.0 | + | 01 February 2025 | | 200.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + | 31 January 2025 | Accrual | 1.76 | 0.0 | 1.76 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + +# --- 3rd disbursement - 1 February, 2025 --- + When Admin successfully disburse the loan on "01 February 2025" with "150" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | 2 | 15 | 31 January 2025 | | 200.74 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 February 2025 | | 150.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 15 | 15 February 2025 | | 638.95 | 211.79 | 2.37 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | + | 4 | 15 | 02 March 2025 | | 426.65 | 212.3 | 1.86 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | + | 5 | 15 | 17 March 2025 | | 213.73 | 212.92 | 1.24 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | + | 6 | 15 | 01 April 2025 | | 0.0 | 213.73 | 0.62 | 0.0 | 0.0 | 214.35 | 0.0 | 0.0 | 0.0 | 214.35 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 950.0 | 7.85 | 0.0 | 0.0 | 957.85 | 0.0 | 0.0 | 0.0 | 957.85 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 300.0 | | + | 01 February 2025 | 01 February 2025 | 500.0 | 1000.0 | + | 01 February 2025 | 01 February 2025 | 150.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + | 31 January 2025 | Accrual | 1.76 | 0.0 | 1.76 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | + | 01 February 2025 | Disbursement | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | 950.0 | false | false | + Then Admin fails to disburse the loan on "01 February 2025" with "50" amount + + When Loan Pay-off is made on "1 February 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4118 + Scenario: Verify cumulative multidisb loan with 2nd disb at 1st installment with flat interest type and same_as_repeyment interest calculation period - UC1 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | +# -- 2nd disb - on Jan, 15, 2025 --# + When Admin sets the business date to "15 January 2025" + When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | +# --- undo last disbursement --- # + When Admin successfully undo last disbursal + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + + When Loan Pay-off is made on "15 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4119 + Scenario: Verify cumulative multidisbursal loan with 2nd disb at 2nd installment with flat interest type and same_as_repeyment interest calculation period - UC2 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | +# -- 2nd disb - on Feb, 15, 2025 --# + When Admin sets the business date to "15 February 2025" + When Admin successfully disburse the loan on "15 February 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 8.75 | 0.0 | 0.0 | 342.08 | 0.0 | 0.0 | 0.0 | 342.08 | + | | | 15 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 28 | 01 March 2025 | | 666.67 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 666.67 | 8.75 | 0.0 | 0.0 | 675.42 | 0.0 | 0.0 | 0.0 | 675.42 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 15 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | +# --- undo disbursement --- # + When Admin successfully undo disbursal + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | + Then Loan Transactions tab has none transaction + + Then Admin can successfully undone the loan approval + Then Loan status will be "SUBMITTED_AND_PENDING_APPROVAL" + And Admin successfully rejects the loan on "15 February 2025" + Then Loan status will be "REJECTED" + + @TestRailId:C4120 + Scenario: Verify cumulative multidisbursal loan with repayment between disbursements with flat interest type and same_as_repeyment interest calculation period - UC3 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | +# -- repayment on Feb, 1, 2025 ---# + When Admin sets the business date to "01 February 2025" + And Customer makes "AUTOPAY" repayment on "01 February 2025" with 339.16 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | 01 February 2025 | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 339.16 | 0.0 | 0.0 | 0.0 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 339.16 | 0.0 | 0.0 | 678.34 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 February 2025 | Repayment | 339.16 | 333.33 | 5.83 | 0.0 | 0.0 | 666.67 | false | false | +# -- 2nd disb - on Feb, 15, 2025 --# + When Admin sets the business date to "15 February 2025" + When Admin successfully disburse the loan on "15 February 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 8.75 | 0.0 | 0.0 | 342.08 | 339.16 | 0.0 | 0.0 | 2.92 | + | | | 15 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 28 | 01 March 2025 | | 666.67 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 666.67 | 8.75 | 0.0 | 0.0 | 675.42 | 0.0 | 0.0 | 0.0 | 675.42 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 339.16 | 0.0 | 0.0 | 1187.09 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 February 2025 | Repayment | 339.16 | 330.41 | 8.75 | 0.0 | 0.0 | 669.59 | false | true | + | 15 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1169.59 | false | false | + + When Loan Pay-off is made on "15 February 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4121 + Scenario: Verify cumulative multidisbursal loan with flat interest type and same_as_repeyment interest calculation period with down payment - UC4 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | + | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_MULTIDISB_AUTO_DOWNPAYMENT | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2025 | | 500.0 | 250.0 | 5.75 | 0.0 | 0.0 | 255.75 | 0.0 | 0.0 | 0.0 | 255.75 | + | 3 | 28 | 01 March 2025 | | 250.0 | 250.0 | 5.75 | 0.0 | 0.0 | 255.75 | 0.0 | 0.0 | 0.0 | 255.75 | + | 4 | 31 | 01 April 2025 | | 0.0 | 250.0 | 5.76 | 0.0 | 0.0 | 255.76 | 0.0 | 0.0 | 0.0 | 255.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.26 | 0.0 | 0.0 | 1017.26 | 250.0 | 0.0 | 0.0 | 767.26 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2025 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | +# -- 2nd disb - on Jan, 15, 2025 --# + When Admin sets the business date to "15 January 2025" + When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 2 | 0 | 15 January 2025 | 15 January 2025 | 1125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 February 2025 | | 750.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | + | 4 | 28 | 01 March 2025 | | 375.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | + | 5 | 31 | 01 April 2025 | | 0.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 375.0 | 0.0 | 0.0 | 1150.89 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2025 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1250.0 | false | false | + | 15 January 2025 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 1125.0 | false | false | + + When Loan Pay-off is made on "15 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4122 + Scenario: Verify cumulative multidisbursal loan with flat interest type and same_as_repeyment interest calculation period with approved over applied amount - UC5 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | + | LP1_INTEREST_FLAT_SAR_RECALCULATION_DAILY_360_30_APPROVED_OVER_APPLIED_MULTIDISB | 01 January 2025 | 1000 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1200" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | +# -- 2nd disb - on Feb, 15, 2025 --# + When Admin sets the business date to "15 January 2025" + When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 |1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 01 January 2025 | 15 January 2025 | 500.0 | | + + When Loan Pay-off is made on "15 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4123 + Scenario: Verify cumulative multidisbursal loan with undo last disb with flat interest type and daily interest calculation period - UC6 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | + | LP1_INTEREST_FLAT_DAILY_RECALCULATION_DAILY_360_30_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | +# -- 2nd disb - on Feb, 15, 2025 --# + When Admin sets the business date to "15 January 2025" + When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 01 January 2025 | 15 January 2025 | 500.0 | | +# --- undo last disbursement --- # + When Admin successfully undo last disbursal + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | + | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + + When Loan Pay-off is made on "15 January 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4201 + Scenario: Verify repayment reversal after adding NSF fee charge with transaction reprocessing + When Admin sets the business date to "06 November 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY | 21 August 2025 | 102.47 | 11.3 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "21 August 2025" with "102.47" amount and expected disbursement date on "21 August 2025" + And Admin successfully disburse the loan on "21 August 2025" with "102.47" EUR transaction amount + And Customer makes "AUTOPAY" repayment on "21 September 2025" with 34.80 EUR transaction amount + When Customer undo "1"th "Repayment" transaction made on "21 September 2025" + And Customer makes "AUTOPAY" repayment on "26 September 2025" with 34.79 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "29 September 2025" with 0.01 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "30 September 2025" with 71.63 EUR transaction amount + When Admin adds "LOAN_NSF_FEE" due date charge with "30 September 2025" due date and 2.8 EUR transaction amount + When Customer undo "1"th "Repayment" transaction made on "26 September 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 21 August 2025 | | 102.47 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 21 September 2025 | 30 September 2025 | 68.63 | 33.84 | 0.96 | 0.0 | 0.0 | 34.8 | 34.8 | 0.0 | 34.8 | 0.0 | + | 2 | 30 | 21 October 2025 | | 34.35 | 34.28 | 0.52 | 0.0 | 2.8 | 37.6 | 36.84 | 36.84 | 0.0 | 0.76 | + | 3 | 31 | 21 November 2025 | | 0.0 | 34.35 | 0.32 | 0.0 | 0.0 | 34.67 | 0.0 | 0.0 | 0.0 | 34.67 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 102.47 | 1.8 | 0.0 | 2.8 | 107.07 | 71.64 | 36.84 | 34.8 | 35.43 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 21 August 2025 | Disbursement | 102.47 | 0.0 | 0.0 | 0.0 | 0.0 | 102.47 | false | false | + | 21 September 2025 | Repayment | 34.8 | 33.84 | 0.96 | 0.0 | 0.0 | 68.63 | true | false | + | 21 September 2025 | Accrual Activity | 0.96 | 0.0 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + | 26 September 2025 | Repayment | 34.79 | 33.84 | 0.95 | 0.0 | 0.0 | 68.63 | true | false | + | 29 September 2025 | Repayment | 0.01 | 0.01 | 0.0 | 0.0 | 0.0 | 102.46 | false | true | + | 30 September 2025 | Repayment | 71.63 | 67.87 | 0.96 | 0.0 | 2.8 | 34.59 | false | true | + | 21 October 2025 | Accrual Activity | 3.32 | 0.0 | 0.52 | 0.0 | 2.8 | 0.0 | false | true | + | 06 November 2025 | Accrual | 1.21 | 0.0 | 1.21 | 0.0 | 0.0 | 0.0 | false | false | + And Customer makes "AUTOPAY" repayment on "06 November 2025" with 35.28 EUR transaction amount + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + + @TestRailId:C4124 + Scenario: Verify cumulative multidisbursal loan that expects tranches with flat interest type and daily interest calculation period - UC7 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | + | LP1_INTEREST_FLAT_DAILY_RECALCULATION_SAR_MULTIDISB_EXPECT_TRANCHES | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | + Then Loan Transactions tab has none transaction + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 15 January 2025 | | 500.0 | | +# -- 2nd disb - on Jan, 15, 2025 --# + When Admin sets the business date to "15 January 2025" + When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 15 January 2025 | 15 January 2025 | 500.0 | | + When Loan Pay-off is made on "15 January 2025" + Then Loan's all installments have obligations met + + @TestRailId:C4227 + Scenario: Verify cumulative multidisbursal loan that expects tranches with flat interest type and no interest calculation period - UC7.1 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with disbursements details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | + | LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | + And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | + Then Loan Transactions tab has none transaction + When Admin disburses the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 15 January 2025 | | 500.0 | | + When Admin sets the business date to "16 January 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | + When Admin sets the business date to "01 February 2025" + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | +# -- 2nd disbursement - on Feb, 1, 2025 --# + When Admin disburses the loan on "01 February 2025" with "500" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2025 | 01 January 2025 | 1000.0 | | + | 15 January 2025 | 01 February 2025 | 500.0 | | + Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" + When Loan Pay-off is made on "01 February 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2025 | 01 February 2025 | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 0.0 | 0.0 | 0.0 | + | 2 | 28 | 01 March 2025 | 01 February 2025 | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 508.63 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2025 | 01 February 2025 | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 508.63 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 1525.89 | 1017.26 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + | 01 February 2025 | Repayment | 1525.89 | 1500.0 | 25.89 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2025 | Accrual | 25.89 | 0.0 | 25.89 | 0.0 | 0.0 | 0.0 | false | false | + + @TestRailId:C4643 + Scenario: Verify that changedTerms is false in LoanDisbursalTransactionBusinessEvent for initial disbursement + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" + When Admin disburses the loan on "01 January 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 15 | 16 January 2024 | | 667.64 | 332.36 | 2.92 | 0.0 | 0.0 | 335.28 | 0.0 | 0.0 | 0.0 | 335.28 | + | 2 | 15 | 31 January 2024 | | 334.31 | 333.33 | 1.95 | 0.0 | 0.0 | 335.28 | 0.0 | 0.0 | 0.0 | 335.28 | + | 3 | 15 | 15 February 2024 | | 0.0 | 334.31 | 0.98 | 0.0 | 0.0 | 335.29 | 0.0 | 0.0 | 0.0 | 335.29 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 5.85 | 0.0 | 0.0 | 1005.85 | 0.0 | 0.0 | 0.0 | 1005.85 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" + + @TestRailId:C4645 + Scenario: Verify that changedTerms is false in LoanDisbursalTransactionBusinessEvent when additional disbursement does not change terms + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE | 01 January 2024 | 300 | 9.4822 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "300" amount and expected disbursement date on "01 January 2024" + When Admin disburses the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.66 | 16.34 | 0.79 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + | 2 | 29 | 01 March 2024 | | 67.19 | 16.47 | 0.66 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + | 3 | 31 | 01 April 2024 | | 50.59 | 16.6 | 0.53 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + | 4 | 30 | 01 May 2024 | | 33.86 | 16.73 | 0.4 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + | 5 | 31 | 01 June 2024 | | 17.0 | 16.86 | 0.27 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + | 6 | 30 | 01 July 2024 | | 0.0 | 17.0 | 0.13 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.78 | 0.0 | 0.0 | 102.78 | 0.0 | 0.0 | 0.0 | 102.78 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" + When Admin sets the business date to "08 January 2024" + When Admin disburses the loan on "08 January 2024" with "200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 08 January 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 250.68 | 49.32 | 2.01 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + | 2 | 29 | 01 March 2024 | | 201.33 | 49.35 | 1.98 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + | 3 | 31 | 01 April 2024 | | 151.59 | 49.74 | 1.59 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + | 4 | 30 | 01 May 2024 | | 101.46 | 50.13 | 1.2 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + | 5 | 31 | 01 June 2024 | | 50.93 | 50.53 | 0.8 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + | 6 | 30 | 01 July 2024 | | 0.0 | 50.93 | 0.4 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 300.0 | 7.98 | 0.0 | 0.0 | 307.98 | 0.0 | 0.0 | 0.0 | 307.98 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 08 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | + Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" + When Loan Pay-off is made on "08 January 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C70224 + Scenario: Verify max disb amount validation in case multidisb loan that expect tranches with overapplied setting enabled - UC1 + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | + And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 + Then Loan status will be "APPROVED" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 2 | 29 | 01 March 2024 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 3 | 31 | 01 April 2024 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 4 | 30 | 01 May 2024 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 5 | 31 | 01 June 2024 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 6 | 30 | 01 July 2024 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | | | 05 January 2024 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 1002.77 | 197.23 | 6.85 | 0.0 | 0.0 | 204.08 | 0.0 | 0.0 | 0.0 | 204.08 | + | 2 | 29 | 01 March 2024 | | 804.54 | 198.23 | 5.85 | 0.0 | 0.0 | 204.08 | 0.0 | 0.0 | 0.0 | 204.08 | + | 3 | 31 | 01 April 2024 | | 605.15 | 199.39 | 4.69 | 0.0 | 0.0 | 204.08 | 0.0 | 0.0 | 0.0 | 204.08 | + | 4 | 30 | 01 May 2024 | | 404.6 | 200.55 | 3.53 | 0.0 | 0.0 | 204.08 | 0.0 | 0.0 | 0.0 | 204.08 | + | 5 | 31 | 01 June 2024 | | 202.88 | 201.72 | 2.36 | 0.0 | 0.0 | 204.08 | 0.0 | 0.0 | 0.0 | 204.08 | + | 6 | 30 | 01 July 2024 | | 0.0 | 202.88 | 1.18 | 0.0 | 0.0 | 204.06 | 0.0 | 0.0 | 0.0 | 204.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 24.46 | 0.0 | 0.0 | 1224.46 | 0.0 | 0.0 | 0.0 | 1224.46 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 + Then Admin fails to disburse the loan on "1 January 2024" with "1600" EUR transaction amount because of wrong amount + Then Admin fails to disburse the loan on "1 January 2024" with "1500" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "1 January 2024" with "1300" EUR transaction amount + Then Loan has availableDisbursementAmountWithOverApplied field with value: 0 + Then Loan status will be "ACTIVE" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 05 January 2024 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 1286.47 | 213.53 | 7.58 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + | 2 | 29 | 01 March 2024 | | 1071.7 | 214.77 | 6.34 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + | 3 | 31 | 01 April 2024 | | 855.67 | 216.03 | 5.08 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + | 4 | 30 | 01 May 2024 | | 638.38 | 217.29 | 3.82 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + | 5 | 31 | 01 June 2024 | | 419.83 | 218.55 | 2.56 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + | 6 | 30 | 01 July 2024 | | 200.0 | 219.83 | 1.28 | 0.0 | 0.0 | 221.11 | 0.0 | 0.0 | 0.0 | 221.11 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1300.0 | 26.66 | 0.0 | 0.0 | 1326.66 | 0.0 | 0.0 | 0.0 | 1326.66 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1300.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | 01 January 2024 | 1300.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + When Admin sets the business date to "5 January 2024" + Then Admin fails to disburse the loan on "5 January 2024" with "300" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "5 January 2024" with "200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1300.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 05 January 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 1253.5 | 246.5 | 8.6 | 0.0 | 0.0 | 255.1 | 0.0 | 0.0 | 0.0 | 255.1 | + | 2 | 29 | 01 March 2024 | | 1005.71 | 247.79 | 7.31 | 0.0 | 0.0 | 255.1 | 0.0 | 0.0 | 0.0 | 255.1 | + | 3 | 31 | 01 April 2024 | | 756.48 | 249.23 | 5.87 | 0.0 | 0.0 | 255.1 | 0.0 | 0.0 | 0.0 | 255.1 | + | 4 | 30 | 01 May 2024 | | 505.79 | 250.69 | 4.41 | 0.0 | 0.0 | 255.1 | 0.0 | 0.0 | 0.0 | 255.1 | + | 5 | 31 | 01 June 2024 | | 253.64 | 252.15 | 2.95 | 0.0 | 0.0 | 255.1 | 0.0 | 0.0 | 0.0 | 255.1 | + | 6 | 30 | 01 July 2024 | | 0.0 | 253.64 | 1.48 | 0.0 | 0.0 | 255.12 | 0.0 | 0.0 | 0.0 | 255.12 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 30.62 | 0.0 | 0.0 | 1530.62 | 0.0 | 0.0 | 0.0 | 1530.62 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1300.0 | false | false | + | 05 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + # --- close loan --- # + When Loan Pay-off is made on "5 January 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C70225 + Scenario: Verify max disb amount validation in case multidisb loan that expect tranches with overapplied setting enabled - UC2 + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + When Admin creates a fully customized loan with disbursement details and following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | + | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | + And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 + Then Loan status will be "APPROVED" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 2 | 29 | 01 March 2024 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 3 | 31 | 01 April 2024 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 4 | 30 | 01 May 2024 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 5 | 31 | 01 June 2024 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | + | 6 | 30 | 01 July 2024 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | | 1000.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + And Admin checks available disbursement amount 0.0 EUR + Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 + Then Admin fails to disburse the loan on "1 January 2024" with "1600" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "1 January 2024" with "1100" EUR transaction amount + Then Loan status will be "ACTIVE" + Then Loan has availableDisbursementAmountWithOverApplied field with value: 200 + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 05 January 2024 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 1119.33 | 180.67 | 6.42 | 0.0 | 0.0 | 187.09 | 0.0 | 0.0 | 0.0 | 187.09 | + | 2 | 29 | 01 March 2024 | | 937.6 | 181.73 | 5.36 | 0.0 | 0.0 | 187.09 | 0.0 | 0.0 | 0.0 | 187.09 | + | 3 | 31 | 01 April 2024 | | 754.81 | 182.79 | 4.3 | 0.0 | 0.0 | 187.09 | 0.0 | 0.0 | 0.0 | 187.09 | + | 4 | 30 | 01 May 2024 | | 570.96 | 183.85 | 3.24 | 0.0 | 0.0 | 187.09 | 0.0 | 0.0 | 0.0 | 187.09 | + | 5 | 31 | 01 June 2024 | | 386.03 | 184.93 | 2.16 | 0.0 | 0.0 | 187.09 | 0.0 | 0.0 | 0.0 | 187.09 | + | 6 | 30 | 01 July 2024 | | 200.0 | 186.03 | 1.09 | 0.0 | 0.0 | 187.12 | 0.0 | 0.0 | 0.0 | 187.12 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1100.0 | 22.57 | 0.0 | 0.0 | 1122.57 | 0.0 | 0.0 | 0.0 | 1122.57 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1100.0 | false | false | + Then Loan Tranche Details tab has the following data: + | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | + | 01 January 2024 | 01 January 2024 | 1100.0 | | + | 05 January 2024 | | 200.0 | 1200.0 | + When Admin sets the business date to "5 January 2024" + Then Admin fails to disburse the loan on "5 January 2024" with "800" EUR transaction amount because of wrong amount + And Admin successfully disburse the loan on "5 January 2024" with "400" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 1100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | | | 05 January 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 1253.37 | 246.63 | 8.45 | 0.0 | 0.0 | 255.08 | 0.0 | 0.0 | 0.0 | 255.08 | + | 2 | 29 | 01 March 2024 | | 1005.6 | 247.77 | 7.31 | 0.0 | 0.0 | 255.08 | 0.0 | 0.0 | 0.0 | 255.08 | + | 3 | 31 | 01 April 2024 | | 756.39 | 249.21 | 5.87 | 0.0 | 0.0 | 255.08 | 0.0 | 0.0 | 0.0 | 255.08 | + | 4 | 30 | 01 May 2024 | | 505.72 | 250.67 | 4.41 | 0.0 | 0.0 | 255.08 | 0.0 | 0.0 | 0.0 | 255.08 | + | 5 | 31 | 01 June 2024 | | 253.59 | 252.13 | 2.95 | 0.0 | 0.0 | 255.08 | 0.0 | 0.0 | 0.0 | 255.08 | + | 6 | 30 | 01 July 2024 | | 0.0 | 253.59 | 1.48 | 0.0 | 0.0 | 255.07 | 0.0 | 0.0 | 0.0 | 255.07 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 30.47 | 0.0 | 0.0 | 1530.47 | 0.0 | 0.0 | 0.0 | 1530.47 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 1100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1100.0 | false | false | + | 05 January 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | + # --- close loan --- # + When Loan Pay-off is made on "5 January 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature deleted file mode 100644 index fbcc73ade93..00000000000 --- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature +++ /dev/null @@ -1,9096 +0,0 @@ -@LoanFeature -Feature: Loan - - @TestRailId:C16 @Smoke - Scenario: Loan creation functionality in Fineract - When Admin sets the business date to the actual date - When Admin creates a client with random data - When Admin creates a new Loan - - @TestRailId:C17 - Scenario: Loan creation functionality in Fineract - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - - @TestRailId:C42 - Scenario: As a user I would like to see that the loan is not created if the loan submission date is after the business date - When Admin sets the business date to "25 June 2022" - When Admin creates a client with random data - Then Admin fails to create a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - - @TestRailId:C43 - Scenario: As a user I would like to see that the loan is created if the loan submission date is equal to business date - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - - @TestRailId:C46 - Scenario: As a user I would like to see that the loan is approved at the business date - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" - - @TestRailId:C30 @single - Scenario: As a user I would like to see that the loan is cannot be approved with future approval date - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - Then Admin fails to approve the loan on "2 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" because of wrong date - - @TestRailId:C47 @multi - Scenario: As a user I would like to see that the loan can be disbursed at the business date - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" - When Admin successfully disburse the loan on "1 July 2022" with "5000" EUR transaction amount - - @TestRailId:C31 - Scenario: As a user I would like to see that the loan is cannot be disbursed with future disburse date - When Admin sets the business date to "1 July 2022" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 July 2022", with Principal: "5000", a loanTermFrequency: 24 months, and numberOfRepayments: 24 - And Admin successfully approves the loan on "1 July 2022" with "5000" amount and expected disbursement date on "2 July 2022" - Then Admin fails to disburse the loan on "2 July 2022" with "5000" EUR transaction amount because of wrong date - - @TestRailId:C64 - Scenario: As a user I would like to see that 50% over applied amount can be approved and disbursed on loan correctly - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" - When Admin successfully disburse the loan on "1 September 2022" with "1500" EUR transaction amount - - @TestRailId:C65 - Scenario: As a user I would like to see that 50% over applied amount can be approved but more than 50% cannot be disbursed on loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" - Then Admin fails to disburse the loan on "1 September 2022" with "1501" EUR transaction amount because of wrong amount - - @TestRailId:C66 - Scenario: As a user I would like to see that more than 50% over applied amount can not be approved on loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - Then Admin fails to approve the loan on "1 September 2022" with "1501" amount and expected disbursement date on "1 September 2022" because of wrong amount - - @TestRailId:C2769 - Scenario: As a user I would like to see that more than 50% over applied amount in total can not be disbursed on loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1500" amount and expected disbursement date on "1 September 2022" - And Admin successfully disburse the loan on "1 September 2022" with "1400" EUR transaction amount - Then Admin fails to disburse the loan on "1 September 2022" with "101" EUR transaction amount because of wrong amount - - @TestRailId:C3767 - Scenario: Verify disbursed amount exceeds approved over applied amount for progressive loan with percentage overAppliedCalculationType - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1300" amount and expected disbursement date on "1 September 2022" - Then Loan has availableDisbursementAmountWithOverApplied field with value: 1500 - And Admin successfully disburse the loan on "1 September 2022" with "1200" EUR transaction amount - Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 - Then Admin fails to disburse the loan on "1 September 2022" with "301" EUR transaction amount because of wrong amount - - When Loan Pay-off is made on "1 September 2022" - Then Loan's all installments have obligations met - - @TestRailId:C3768 - Scenario: Verify approved amount exceeds approved over applied amount for progressive loan with flat overAppliedCalculationType - When Admin sets the business date to "1 January 2024" - And Admin creates a client with random data - And Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_FLAT_CAPITALIZED_INCOME | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - Then Admin fails to approve the loan on "1 January 2024" with "2001" amount and expected disbursement date on "1 January 2024" because of wrong amount - - And Admin successfully rejects the loan on "1 January 2024" - Then Loan status will be "REJECTED" - - @TestRailId:C3769 - Scenario: Verify disbursed amount exceeds approved over applied amount for progressive loan with flat overAppliedCalculationType - When Admin sets the business date to "1 January 2024" - And Admin creates a client with random data - And Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "1 January 2024" with "9000" amount and expected disbursement date on "1 January 2024" - Then Loan has availableDisbursementAmountWithOverApplied field with value: 11000 - And Admin successfully disburse the loan on "1 January 2024" with "9900" EUR transaction amount - Then Loan has availableDisbursementAmountWithOverApplied field with value: 1100 - Then Admin fails to disburse the loan on "1 January 2024" with "1200" EUR transaction amount because of wrong amount - - When Loan Pay-off is made on "1 January 2024" - Then Loan's all installments have obligations met - - @TestRailId:C3895 - Scenario: Verify disbursed amount approved over applied amount for progressive loan that expects tranches with percentage overAppliedCalculationType - UC1 - When Admin sets the business date to "1 January 2024" - And Admin creates a client with random data - When Admin creates a fully customized loan with disbursement details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | - | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | - And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" - Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 - Then Loan status will be "APPROVED" - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | | 1000.0 | | - When Admin sets the business date to "2 January 2024" - And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | | 1000.0 | | - | 05 January 2024 | | 200.0 | 1200.0 | - And Admin checks available disbursement amount 0.0 EUR - Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 - Then Admin fails to disburse the loan on "2 January 2024" with "1600" EUR transaction amount because of wrong amount - And Admin successfully disburse the loan on "2 January 2024" with "1500" EUR transaction amount - Then Loan has availableDisbursementAmountWithOverApplied field with value: 0 - Then Loan status will be "ACTIVE" - And Admin checks available disbursement amount 0.0 EUR - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | 02 January 2024 | 1500.0 | | - | 05 January 2024 | | 200.0 | 1200.0 | - - When Loan Pay-off is made on "2 January 2024" - Then Loan's all installments have obligations met - - @TestRailId:C3896 - Scenario: Verify disbursed amount approved over applied amount for progressive loan that expects tranches with percentage overAppliedCalculationType - UC2 - When Admin sets the business date to "1 January 2024" - And Admin creates a client with random data - When Admin creates a fully customized loan with disbursement details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | - | LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000.0 | - And Admin successfully approves the loan on "1 January 2024" with "1200" amount and expected disbursement date on "1 January 2024" - Then Loan has availableDisbursementAmountWithOverApplied field with value: 500 - Then Loan status will be "APPROVED" - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | | 1000.0 | | - When Admin sets the business date to "2 January 2024" - And Admin successfully add disbursement detail to the loan on "5 January 2024" with 200 EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | | 1000.0 | | - | 05 January 2024 | | 200.0 | 1200.0 | - And Admin checks available disbursement amount 0.0 EUR - Then Loan has availableDisbursementAmountWithOverApplied field with value: 300 - Then Admin fails to disburse the loan on "2 January 2024" with "1600" EUR transaction amount because of wrong amount - And Admin successfully disburse the loan on "2 January 2024" with "1100" EUR transaction amount - Then Loan status will be "ACTIVE" - And Admin checks available disbursement amount 100.0 EUR - Then Loan has availableDisbursementAmountWithOverApplied field with value: 200 - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2024 | 02 January 2024 | 1100.0 | | - | 05 January 2024 | | 200.0 | 1200.0 | - - When Loan Pay-off is made on "2 January 2024" - Then Loan's all installments have obligations met - - @TestRailId:C67 - Scenario: As admin I would like to check that amounts are distributed equally in loan repayment schedule - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - Then Amounts are distributed equally in loan repayment schedule in case of total amount 1000 - When Admin successfully disburse the loan on "1 September 2022" with "900" EUR transaction amount - Then Amounts are distributed equally in loan repayment schedule in case of total amount 900 - - @TestRailId:C68 - Scenario: As admin I would like to be sure that approval of on loan can be undone - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - Then Admin can successfully undone the loan approval - - @TestRailId:C69 - Scenario: As admin I would like to be sure that disbursal of on loan can be undone - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount - Then Admin can successfully undone the loan disbursal - Then Admin can successfully undone the loan approval - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - - @TestRailId:C70 - Scenario: As admin I would like to be sure that submitted on date can be edited on loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 September 2022" - Then Admin can successfully modify the loan and changes the submitted on date to "31 August 2022" - - @TestRailId:C2454 @fraud - Scenario: As admin I would like to set Fraud flag to a loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount - Then Admin can successfully set Fraud flag to the loan - - @TestRailId:C2455 @fraud - Scenario: As admin I would like to unset Fraud flag to a loan - When Admin sets the business date to "1 September 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "1 September 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "1 September 2022" with "1000" amount and expected disbursement date on "1 September 2022" - When Admin successfully disburse the loan on "1 September 2022" with "1000" EUR transaction amount - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - - - @TestRailId:C2456 @fraud - Scenario: As admin I would like to try to add fraud flag on a not active loan - When Admin sets the business date to "25 October 2022" - When Admin creates a client with random data - When Admin successfully creates a new customised Loan submitted on date: "25 October 2022", with Principal: "1000", a loanTermFrequency: 3 months, and numberOfRepayments: 3 - And Admin successfully approves the loan on "25 October 2022" with "1000" amount and expected disbursement date on "25 October 2022" - Then Admin can successfully unset Fraud flag to the loan - - @TestRailId:C2473 @idempotency - Scenario: As admin I would like to verify that idempotency APIs can be called with the Idempotency-Key header - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - Then Loan has 1 "DISBURSEMENT" transactions on Transactions tab - Then Loan has 1 "REPAYMENT" transactions on Transactions tab - - @TestRailId:C2474 @idempotency - Scenario: As admin I would like to verify that idempotency APIs can be called without the Idempotency-Key header - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and system-generated Idempotency key - Then Loan has 1 "DISBURSEMENT" transactions on Transactions tab - Then Loan has 1 "REPAYMENT" transactions on Transactions tab - - @TestRailId:C2475 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of REPAYMENT transaction - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 200 EUR value for transaction amount - Then Transaction response has the correct clientId and the loanId of the first transaction - Then Loan has 1 "REPAYMENT" transactions on Transactions tab - - @TestRailId:C2476 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of GOODWILL_CREDIT transaction - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 1000 EUR transaction amount and system-generated Idempotency key - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 200 EUR value for transaction amount - Then Transaction response has the correct clientId and the loanId of the first transaction - Then Loan has 1 "GOODWILL_CREDIT" transactions on Transactions tab - - @TestRailId:C2477 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of PAYOUT_REFUND transaction - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 200 EUR value for transaction amount - Then Transaction response has the correct clientId and the loanId of the first transaction - Then Loan has 1 "PAYOUT_REFUND" transactions on Transactions tab - - @TestRailId:C2478 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in a happy path scenario in case of MERCHANT_ISSUED_REFUND transaction - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 200 EUR value for transaction amount - Then Transaction response has the correct clientId and the loanId of the first transaction - Then Loan has 1 "MERCHANT_ISSUED_REFUND" transactions on Transactions tab - - @TestRailId:C2482 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in case of client calls the same idempotency key on a second loan - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin crates a second default loan with date: "1 November 2022" - And Admin successfully approves the second loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the second loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "REPAYMENT" transaction on the second loan with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 200 EUR value for transaction amount - Then Transaction response has the correct clientId and the loanId of the first transaction - Then Loan has 1 "REPAYMENT" transactions on Transactions tab - Then Second loan has 0 "REPAYMENT" transactions on Transactions tab - -# TODO unskip and check when PS-1106 is done - @Skip @TestRailId:C2483 @idempotency - Scenario: As admin I would like to verify that idempotency applies correctly in case of a second client calls the same idempotency key on a second loan - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin creates a second client with random data - When Admin crates a second default loan for the second client with date: "1 November 2022" - And Admin successfully approves the second loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the second loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - And Customer makes "REPAYMENT" transaction on the second loan with "AUTOPAY" payment type on "15 November 2022" with 300 EUR transaction amount with the same Idempotency key as previous transaction - Then Transaction response has boolean value in header "x-served-from-cache": "true" - Then Transaction response has 300 EUR value for transaction amount - Then Transaction response has the clientId for the second client and the loanId of the second transaction - Then Loan has 1 "REPAYMENT" transactions on Transactions tab - Then Second loan has 1 "REPAYMENT" transactions on Transactions tab - - @TestRailId:C2479 - Scenario: As admin I would like to be sure that goodwill credit transaction is working properly - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - Then Loan has 1 "GOODWILL_CREDIT" transactions on Transactions tab - - @TestRailId:C2480 - Scenario: As admin I would like to be sure that payout refund transaction is working properly - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - Then Loan has 1 "PAYOUT_REFUND" transactions on Transactions tab - - @TestRailId:C2481 - Scenario: As admin I would like to be sure that merchant issued refund transaction is working properly - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "15 November 2022" - And Customer makes "AUTOPAY" repayment on "15 November 2022" with 1000 EUR transaction amount - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 November 2022" with 200 EUR transaction amount and self-generated Idempotency key - Then Loan has 1 "MERCHANT_ISSUED_REFUND" transactions on Transactions tab - - @TestRailId:C2488 - Scenario: As admin I would like to be sure that no multiple status change event got raised during transaction replaying - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - Then Loan status has changed to "Approved" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - Then Loan status has changed to "Active" - When Admin sets the business date to "2 November 2022" - And Customer makes "AUTOPAY" repayment on "2 November 2022" with 500 EUR transaction amount - When Admin sets the business date to "3 November 2022" - And Customer makes "AUTOPAY" repayment on "3 November 2022" with 100 EUR transaction amount - When Admin sets the business date to "4 November 2022" - And Customer makes "AUTOPAY" repayment on "4 November 2022" with 600 EUR transaction amount - Then Loan status has changed to "Overpaid" - When Customer undo "2"th repayment on "4 November 2022" - Then No new event with type "LoanStatusChangedEvent" has been raised for the loan - When Customer undo "1"th repayment on "4 November 2022" - Then Loan status has changed to "Active" - - @TestRailId:C2489 - Scenario: As admin I would like to charge-off a loan and be sure the event was triggered - When Admin sets the business date to "1 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 November 2022" - And Admin successfully approves the loan on "1 November 2022" with "1000" amount and expected disbursement date on "1 November 2022" - When Admin successfully disburse the loan on "1 November 2022" with "1000" EUR transaction amount - When Admin sets the business date to "2 November 2022" - And Customer makes "AUTOPAY" repayment on "2 November 2022" with 500 EUR transaction amount - When Admin sets the business date to "3 November 2022" - And Admin does charge-off the loan on "3 November 2022" - Then Loan marked as charged-off on "03 November 2022" - - @TestRailId:C2491 - Scenario: As a user I would like to do multiple repayment, overpay the loan and reverse-replaying transactions and check outstanding balance - When Admin sets the business date to "01 November 2022" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 November 2022" - And Admin successfully approves the loan on "01 November 2022" with "1000" amount and expected disbursement date on "01 November 2022" - When Admin successfully disburse the loan on "01 November 2022" with "1000" EUR transaction amount - Then Loan has 1000 outstanding amount - When Admin sets the business date to "02 November 2022" - And Customer makes "AUTOPAY" repayment on "02 November 2022" with 500 EUR transaction amount - Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan has 500 outstanding amount - When Admin sets the business date to "03 November 2022" - And Customer makes "AUTOPAY" repayment on "03 November 2022" with 10 EUR transaction amount - Then Loan Transactions tab has a transaction with date: "03 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 490.0 | - Then Loan has 490 outstanding amount - When Admin sets the business date to "04 November 2022" - And Customer makes "AUTOPAY" repayment on "04 November 2022" with 400 EUR transaction amount - Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 90.0 | - Then Loan has 90 outstanding amount - When Admin sets the business date to "05 November 2022" - And Customer makes "AUTOPAY" repayment on "05 November 2022" with 390 EUR transaction amount - Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 390.0 | 90.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan has 300 overpaid amount - When Customer undo "2"th repayment on "04 November 2022" - Then In Loan Transactions the "3"th Transaction has Transaction type="Repayment" and is reverted - Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 390.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan has 290 overpaid amount - When Customer undo "1"th repayment on "04 November 2022" - Then In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted - Then In Loan Transactions the "3"th Transaction has Transaction type="Repayment" and is reverted - Then Loan Transactions tab has a transaction with date: "04 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 600.0 | - Then Loan Transactions tab has a transaction with date: "05 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 390.0 | 390.0 | 0.0 | 0.0 | 0.0 | 210.0 | - Then Loan has 210 outstanding amount - And Customer makes "AUTOPAY" repayment on "02 November 2022" with 500 EUR transaction amount - Then Loan has 290 overpaid amount - - @TestRailId:C2502 - Scenario: Verify that Loan status goes from active to overpaid in case of Goodwill credit transaction when transaction amount is greater than balance - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 300 outstanding amount - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 100 overpaid amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 250.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | - Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | l1 | Overpayment account | | 100.0 | - | EXPENSE | 744003 | Goodwill Expense Account | 400.0 | | - - @TestRailId:C2503 - Scenario: Verify that Loan status goes from active to overpaid in case of Backdated 3rd repayment when transaction amount is greater than balance - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 300 outstanding amount - And Customer makes "AUTOPAY" repayment on "2 January 2023" with 400 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 100 overpaid amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "02 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 400.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 400.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 150.0 | - | LIABILITY | l1 | Overpayment account | | 100.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | - - - @TestRailId:C2504 - Scenario: Verify that Loan status goes from overpaid to active in case of Chargeback transaction when transaction amount is greater than overpaid amount - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 200 overpaid amount - When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 100 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - | ASSET | 112601 | Loans Receivable | | 100.0 | - | LIABILITY | l1 | Overpayment account | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - Then Loan Transactions tab has a "CHARGEBACK" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 100.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 300.0 | - | LIABILITY | l1 | Overpayment account | 200.0 | | - - @TestRailId:C2506 - Scenario: Verify that Loan status goes from overpaid to active in case of 1st repayment is undone - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 200 overpaid amount - When Customer undo "1"th repayment on "3 January 2023" - Then Loan status will be "ACTIVE" - Then Loan has 250 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - | ASSET | 112601 | Loans Receivable | 450.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 450.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - - @TestRailId:C2507 - Scenario: Verify that Loan status goes from active to closed in case of Goodwill credit transaction when transaction amount equals balance - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 300 outstanding amount - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 300 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 250.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | - Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | EXPENSE | 744003 | Goodwill Expense Account | 300.0 | | - - @TestRailId:C2508 - Scenario: Verify that Loan status goes from active to closed in case of Backdated 3rd repayment when transaction amount equals balance - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 250 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 300 outstanding amount - And Customer makes "AUTOPAY" repayment on "2 January 2023" with 300 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "02 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 250.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 250.0 | | - - @TestRailId:C2509 - Scenario: Verify that Loan status goes from closed to overpaid in case of Goodwill credit transaction - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 500 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - When Admin sets the business date to "7 January 2023" - And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "7 January 2023" with 100 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 100 overpaid amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 500.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 500.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | - Then Loan Transactions tab has a "GOODWILL_CREDIT" transaction with date "07 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | LIABILITY | l1 | Overpayment account | | 100.0 | - | EXPENSE | 744003 | Goodwill Expense Account | 100.0 | | - - - @TestRailId:C2510 - Scenario: Verify that Loan status goes from overpaid to closed in case of Chargeback transaction when transaction amount equals overpaid amount - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 450 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 450 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 200 overpaid amount - When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 200 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 450.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 450.0 | | - | ASSET | 112601 | Loans Receivable | | 100.0 | - | LIABILITY | l1 | Overpayment account | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - Then Loan Transactions tab has a "CHARGEBACK" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | LIABILITY | l1 | Overpayment account | 200.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | - - - @TestRailId:C2512 - Scenario: Verify that Loan status goes from overpaid to closed in case of 1st repayment is undone - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 700 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 200 overpaid amount - When Customer undo "1"th repayment on "3 January 2023" - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | - | ASSET | 112601 | Loans Receivable | 200.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 700.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 700.0 | | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - - @TestRailId:C2513 - Scenario: Verify that Loan status goes from closed to active in case of Chargeback transaction - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 500 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 300 EUR transaction amount - When Admin sets the business date to "7 January 2023" - And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 200 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 200 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 500.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 500.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 300.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 300.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | - Then Loan Transactions tab has a "CHARGEBACK" transaction with date "07 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 200.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | - - @TestRailId:C2514 - Scenario: Verify that Loan status goes from closed to active in case of 1st repayment is undone - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "3 January 2023" - And Customer makes "AUTOPAY" repayment on "3 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "5 January 2023" - And Customer makes "AUTOPAY" repayment on "5 January 2023" with 600 EUR transaction amount - When Admin sets the business date to "7 January 2023" - And Customer makes "AUTOPAY" repayment on "7 January 2023" with 200 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - When Customer undo "1"th repayment on "3 January 2023" - Then Loan status will be "ACTIVE" - Then Loan has 200 outstanding amount - Then Loan Transactions tab has a "DISBURSEMENT" transaction with date "01 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | 1000.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 1000.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "03 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | - | ASSET | 112601 | Loans Receivable | 200.0 | | - | LIABILITY | 145023 | Suspense/Clearing account | | 200.0 | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "05 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 600.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 600.0 | | - Then Loan Transactions tab has a "REPAYMENT" transaction with date "07 January 2023" which has the following Journal entries: - | Type | Account code | Account name | Debit | Credit | - | ASSET | 112601 | Loans Receivable | | 200.0 | - | LIABILITY | 145023 | Suspense/Clearing account | 200.0 | | - - - @TestRailId:C2539 - Scenario: Verify that loan overdue calculation is updated upon Goodwill credit transaction - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "1 March 2023" - When Admin runs inline COB job for Loan - Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" - Then Loan status will be "ACTIVE" - Then Loan has 1000 outstanding amount - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key - Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C2540 - Scenario: Verify that loan overdue calculation is updated upon Payout refund transaction - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "1 March 2023" - When Admin runs inline COB job for Loan - Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" - Then Loan status will be "ACTIVE" - Then Loan has 1000 outstanding amount - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key - Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C2541 - Scenario: Verify that loan overdue calculation is updated upon Merchant issued refund transaction - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "1 March 2023" - When Admin runs inline COB job for Loan - Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" - Then Loan status will be "ACTIVE" - Then Loan has 1000 outstanding amount - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "1 March 2023" with 1000 EUR transaction amount and system-generated Idempotency key - Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C2552 - Scenario: Verify that delinquency event contains the correct delinquentDate in case of one repayment is overdue - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "1 January 2023" - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "5 March 2023" - When Admin runs inline COB job for Loan - Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-02-03" - - @TestRailId:C2553 - Scenario: Verify that delinquency event contains the correct delinquentDate in case of multiple repayments are overdue - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 1 January 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "1 January 2023" with "1000" amount and expected disbursement date on "1 January 2023" - When Admin successfully disburse the loan on "1 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "5 April 2023" - When Admin runs inline COB job for Loan - Then Admin checks that delinquency range is: "RANGE_30" and has delinquentDate "2023-02-04" - - @TestRailId:C2583 - Scenario: Verify last payment related fields when retrieving loan details with 1 repayment - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 200.0 | 03 January 2023 | 200.0 | 03 January 2023 | - - @TestRailId:C2586 - Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on different day - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "05 January 2023" - And Customer makes "AUTOPAY" repayment on "05 January 2023" with 300 EUR transaction amount - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 300.0 | 05 January 2023 | 300.0 | 05 January 2023 | - - @TestRailId:C2587 - Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on the same day - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 300 EUR transaction amount - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 300.0 | 03 January 2023 | 300.0 | 03 January 2023 | - - @TestRailId:C2588 - Scenario: Verify last payment related fields when retrieving loan details with 2 repayments on different day then the second repayment reversed - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "05 January 2023" - And Customer makes "AUTOPAY" repayment on "05 January 2023" with 300 EUR transaction amount - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 300.0 | 05 January 2023 | 300.0 | 05 January 2023 | - When Customer undo "1"th transaction made on "05 January 2023" - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 200.0 | 03 January 2023 | 200.0 | 03 January 2023 | - - @TestRailId:C2589 - Scenario: Verify last payment related fields when retrieving loan details with 1 repayment and 1 goodwill credit transaction - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "05 January 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 400.0 | 05 January 2023 | 200.0 | 03 January 2023 | - - @TestRailId:C2590 - Scenario: Verify last payment related fields when retrieving loan details with 1 repayment, 1 goodwill credit transaction and 1 more repayment then the second repayment reversed - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "03 January 2023" - And Customer makes "AUTOPAY" repayment on "03 January 2023" with 200 EUR transaction amount - When Admin sets the business date to "05 January 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "5 January 2023" with 400 EUR transaction amount and system-generated Idempotency key - When Admin sets the business date to "07 January 2023" - And Customer makes "AUTOPAY" repayment on "07 January 2023" with 300 EUR transaction amount - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 300.0 | 07 January 2023 | 300.0 | 07 January 2023 | - When Customer undo "1"th transaction made on "07 January 2023" - Then Loan details has the following last payment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 400.0 | 05 January 2023 | 200.0 | 03 January 2023 | - - @TestRailId:C2678 - Scenario: Verify that after loan is closed loan details and event has last repayment date and amount - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - Then Loan status has changed to "Active" - When Admin sets the business date to "02 January 2023" - And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1000 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan details and event has the following last repayment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 1000.0 | 02 January 2023 | 1000.0 | 02 January 2023 | - - @TestRailId:C2679 - Scenario: Verify that after loan is overpaid loan details and event has last repayment date and amount - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - Then Loan status has changed to "Active" - When Admin sets the business date to "02 January 2023" - And Customer makes "AUTOPAY" repayment on "02 January 2023" with 1100 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan details and event has the following last repayment related data: - | lastPaymentAmount | lastPaymentDate | lastRepaymentAmount | lastRepaymentDate | - | 1100.0 | 02 January 2023 | 1100.0 | 02 January 2023 | - - @TestRailId:C2687 @fraud - Scenario: Verify that closed loan can be marked as Fraud - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "15 January 2023" - And Customer makes "AUTOPAY" repayment on "15 January 2023" with 1000 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Admin can successfully set Fraud flag to the loan - - @TestRailId:C2688 @fraud - Scenario: Verify that overpaid loan can be marked as Fraud - When Admin sets the business date to "01 January 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 January 2023" - And Admin successfully approves the loan on "01 January 2023" with "1000" amount and expected disbursement date on "01 January 2023" - When Admin successfully disburse the loan on "01 January 2023" with "1000" EUR transaction amount - When Admin sets the business date to "15 January 2023" - And Customer makes "AUTOPAY" repayment on "15 January 2023" with 1100 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Admin can successfully set Fraud flag to the loan - - @TestRailId:C2690 - Scenario: Verify that the repayment schedule is correct when the loan has a fee and multi disbursement happens - When Admin sets the business date to "1 May 2023" - When Admin creates a client with random data - And Admin successfully creates a new customised Loan submitted on date: "1 May 2023", with Principal: "1000", a loanTermFrequency: 1 months, and numberOfRepayments: 1 - And Admin successfully approves the loan on "1 May 2023" with "1000" amount and expected disbursement date on "1 May 2023" - And Admin successfully disburse the loan on "1 May 2023" with "750" EUR transaction amount - Then Loan has 750 outstanding amount - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 May 2023" due date and 8 EUR transaction amount - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 June 2023 | | 0.0 | 750.0 | 0.0 | 8.0 | 0.0 | 758.0 | 0.0 | 0.0 | 0.0 | 758.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 750 | 0 | 8 | 0 | 758 | 0 | 0 | 0 | 758 | - And Admin successfully disburse the loan on "1 May 2023" with "750" EUR transaction amount - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 May 2023 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 June 2023 | | 0.0 | 1500.0 | 0.0 | 8.0 | 0.0 | 1508.0 | 0.0 | 0.0 | 0.0 | 1508.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500 | 0 | 8 | 0 | 1508 | 0 | 0 | 0 | 1508 | - - @TestRailId:C2691 - Scenario: As an admin I would like to do a chargeback for Goodwill Credit - When Admin sets the business date to "8 May 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" - And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount - When Admin sets the business date to "9 May 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key - When Admin sets the business date to "10 May 2023" - And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | - | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 09 May 2023 | Goodwill Credit | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | - | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C2692 - Scenario: As an admin I would like to do a chargeback for Payout Refund - When Admin sets the business date to "8 May 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" - And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount - When Admin sets the business date to "9 May 2023" - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key - When Admin sets the business date to "10 May 2023" - And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | - | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 09 May 2023 | Payout Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | - | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C2693 - Scenario: As an admin I would like to do a chargeback for Merchant Issued Refund - When Admin sets the business date to "8 May 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 8 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "8 May 2023" with "1000" amount and expected disbursement date on "8 May 2023" - And Admin successfully disburse the loan on "8 May 2023" with "1000" EUR transaction amount - When Admin sets the business date to "9 May 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "9 May 2023" with 300 EUR transaction amount and system-generated Idempotency key - When Admin sets the business date to "10 May 2023" - And Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 300 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2023 | | 667.0 | 633.0 | 0.0 | 0.0 | 0.0 | 633.0 | 300.0 | 300.0 | 0.0 | 333.0 | - | 2 | 30 | 08 July 2023 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 3 | 31 | 08 August 2023 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1300 | 0 | 0 | 0 | 1300 | 300 | 300 | 0 | 1000 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 08 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 09 May 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 700.0 | - | 10 May 2023 | Chargeback | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C2770 - Scenario: As an admin I would like to do two merchant issued refund and charge adjustment to close the loan - When Global config "charge-accrual-date" value set to "submitted-date" - When Admin sets the business date to "14 May 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 14 May 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST | - And Admin successfully approves the loan on "14 May 2023" with "127.95" amount and expected disbursement date on "14 May 2023" - And Admin successfully disburse the loan on "14 May 2023" with "127.95" EUR transaction amount - When Admin sets the business date to "11 June 2023" - When Batch API call with steps: rescheduleLoan from "13 June 2023" to "13 July 2023" submitted on date: "11 June 2023", approveReschedule on date: "11 June 2023" runs with enclosingTransaction: "true" - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "13 July 2023" due date and 3.65 EUR transaction amount - When Admin sets the business date to "12 June 2023" - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 0.0 | 0.0 | 0.0 | 131.6 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 127.95 | 0 | 3.65 | 0 | 131.60 | 0 | 0 | 0 | 131.60 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | - | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | - When Admin sets the business date to "17 June 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 June 2023" with 125 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 125.0 | 125.0 | 0.0 | 6.6 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 127.95 | 0 | 3.65 | 0 | 131.6 | 125.0 | 125.0 | 0 | 6.60 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | - | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | - | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | - When Admin makes a charge adjustment for the last "LOAN_SNOOZE_FEE" type charge which is due on "13 July 2023" with 3.65 EUR transaction amount and externalId "" - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 60 | 13 July 2023 | | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 128.65 | 128.65 | 0.0 | 2.95 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 127.95 | 0 | 3.65 | 0 | 131.6 | 128.65 | 128.65 | 0 | 2.95 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | - | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | - | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | - | 17 June 2023 | Charge Adjustment | 3.65 | 2.95 | 0.0 | 0.7 | 0.0 | 0.0 | - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 June 2023" with 2.95 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 May 2023 | | 127.95 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 60 | 13 July 2023 | 17 June 2023 | 0.0 | 127.95 | 0.0 | 3.65 | 0.0 | 131.6 | 131.6 | 131.6 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 127.95 | 0 | 3.65 | 0 | 131.6 | 131.6 | 131.6 | 0 | 0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 14 May 2023 | Disbursement | 127.95 | 0.0 | 0.0 | 0.0 | 0.0 | 127.95 | - | 11 June 2023 | Accrual | 3.65 | 0.0 | 0.0 | 3.65 | 0.0 | 0.0 | - | 17 June 2023 | Merchant Issued Refund | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 2.95 | - | 17 June 2023 | Charge Adjustment | 3.65 | 2.95 | 0.0 | 0.7 | 0.0 | 0.0 | - | 17 June 2023 | Merchant Issued Refund | 2.95 | 0.0 | 0.0 | 2.95 | 0.0 | 0.0 | - When Global config "charge-accrual-date" value set to "due-date" - - @TestRailId:C2776 - Scenario: Verify that maturity date is updated on repayment reversal - When Admin sets the business date to "01 June 2023" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 June 2023" - And Admin successfully approves the loan on "01 June 2023" with "1000" amount and expected disbursement date on "01 June 2023" - When Admin successfully disburse the loan on "01 June 2023" with "1000" EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has the following maturity data: - | actualMaturityDate | expectedMaturityDate | - | 01 July 2023 | 01 July 2023 | - When Admin sets the business date to "20 June 2023" - And Customer makes "AUTOPAY" repayment on "20 June 2023" with 1000 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has the following maturity data: - | actualMaturityDate | expectedMaturityDate | - | 20 June 2023 | 01 July 2023 | - When Admin sets the business date to "20 June 2023" - When Customer undo "1"th "Repayment" transaction made on "20 June 2023" - Then Loan status will be "ACTIVE" - Then Loan has the following maturity data: - | actualMaturityDate | expectedMaturityDate | - | 01 July 2023 | 01 July 2023 | - - @TestRailId:C3202 - Scenario: Verify that closed date is updated on repayment reversal - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a new default Loan with date: "01 June 2024" - And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount - Then Loan status will be "ACTIVE" - When Admin sets the business date to "20 June 2024" - And Customer makes "AUTOPAY" repayment on "20 June 2024" with 1000 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan closedon_date is "20 June 2024" - When Admin sets the business date to "21 June 2024" - When Customer undo "1"th "Repayment" transaction made on "20 June 2024" - Then Loan status will be "ACTIVE" - Then Loan closedon_date is "null" - - @TestRailId:C2777 - Scenario: As an admin I would like to delete a loan using external id - When Admin sets the business date to the actual date - And Admin creates a client with random data - When Admin creates a new Loan - Then Admin successfully deletes the loan with external id - - @TestRailId:C2778 - Scenario: As an admin I would like to verify that deleting loan using incorrect external id gives error - When Admin sets the business date to the actual date - And Admin creates a client with random data - When Admin creates a new Loan - Then Admin fails to delete the loan with incorrect external id - - @TestRailId:C2784 - Scenario: As a user I would like to do multiple repayment after reverse transactions and check the order of transactions - When Admin sets the business date to "01 November 2022" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 01 November 2022 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | DUE_PENALTY_FEE_INTEREST_PRINCIPAL_IN_ADVANCE_PRINCIPAL_PENALTY_FEE_INTEREST | - And Admin successfully approves the loan on "01 November 2022" with "1000" amount and expected disbursement date on "01 November 2022" - When Admin successfully disburse the loan on "01 November 2022" with "1000" EUR transaction amount - Then Loan has 1000 outstanding amount - When Admin adds "LOAN_NSF_FEE" due date charge with "2 November 2022" due date and 10 EUR transaction amount - When Admin sets the business date to "02 November 2022" - And Customer makes "AUTOPAY" repayment on "02 November 2022" with 9 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "02 November 2022" with 8 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "02 November 2022" with 7 EUR transaction amount - Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | - | Repayment | 8.0 | 7.0 | 0.0 | 0.0 | 1.0 | 993.0 | - | Repayment | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | 986.0 | - When Customer undo "1"th repayment on "02 November 2022" - Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | - | Repayment | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 1000.0 | - | Repayment | 7.0 | 5.0 | 0.0 | 0.0 | 2.0 | 993.0 | - When Customer undo "2"th repayment on "02 November 2022" - Then Loan Transactions tab has a transaction with date: "02 November 2022", and with the following data: - | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | Repayment | 9.0 | 0.0 | 0.0 | 0.0 | 9.0 | 1000.0 | - | Repayment | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 1000.0 | - | Repayment | 7.0 | 0.0 | 0.0 | 0.0 | 7.0 | 1000.0 | - - @TestRailId:C2783 - Scenario: As an admin I would like to verify that only one active repayment schedule exits for loan multiple disbursement - When Admin sets the business date to "07 July 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 07 July 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "07 July 2023" with "1000" amount and expected disbursement date on "07 July 2023" - And Admin successfully disburse the loan on "07 July 2023" with "370.55" EUR transaction amount - When Admin sets the business date to "12 July 2023" - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 July 2023" due date and 5.15 EUR transaction amount - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 06 August 2023 | | 0.0 | 370.55 | 0.0 | 5.15 | 0.0 | 375.7 | 0.0 | 0.0 | 0.0 | 375.7 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 370.55 | 0 | 5.15 | 0 | 375.70 | 0 | 0 | 0 | 375.70 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | - | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | - When Admin sets the business date to "21 July 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "21 July 2023" with 167.4 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 06 August 2023 | | 0.0 | 370.55 | 0.0 | 5.15 | 0.0 | 375.7 | 167.4 | 167.4 | 0.0 | 208.3 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 370.55 | 0 | 5.15 | 0 | 375.7 | 167.4 | 167.4 | 0 | 208.3 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | - | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | - | 21 July 2023 | Merchant Issued Refund | 167.4 | 162.25 | 0.0 | 5.15 | 0.0 | 208.3 | - When Admin runs inline COB job for Loan - When Admin sets the business date to "24 July 2023" - And Admin successfully disburse the loan on "24 July 2023" with "18" EUR transaction amount - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 07 July 2023 | | 370.55 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 24 July 2023 | | 18.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 06 August 2023 | | 0.0 | 388.55 | 0.0 | 5.15 | 0.0 | 393.7 | 167.4 | 167.4 | 0.0 | 226.3 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 388.55 | 0 | 5.15 | 0 | 393.7 | 167.4 | 167.4 | 0 | 226.3 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 07 July 2023 | Disbursement | 370.55 | 0.0 | 0.0 | 0.0 | 0.0 | 370.55 | - | 11 July 2023 | Accrual | 5.15 | 0.0 | 0.0 | 5.15 | 0.0 | 0.0 | - | 21 July 2023 | Merchant Issued Refund | 167.4 | 162.25 | 0.0 | 5.15 | 0.0 | 208.3 | - | 24 July 2023 | Disbursement | 18.0 | 0.0 | 0.0 | 0.0 | 0.0 | 226.3 | - - @TestRailId:C2842 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that simple payments are working with advanced payment allocation (UC1) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 250.0 | 0 | 0 | 250.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 375.0 | 0 | 0 | 125.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 15 February 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2843 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that simple payments and overpayment of the installment (goes to next installment) are working with advanced payment allocation (UC2) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 275.0 | 25.0 | 0 | 225.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 50.0 | 0 | 100.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 50.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - | 31 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 15 February 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2844 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that simple payments and overpayment of the installment (goes to last installment) are working with advanced payment allocation (UC3) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "16 January 2023" with 150 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 275.0 | 25.0 | 0 | 225.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - When Admin sets the business date to "31 January 2023" - And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "31 January 2023" with 125 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 25.0 | 0 | 100.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - | 31 January 2023 | Goodwill Credit | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | - When Admin sets the business date to "15 February 2023" - And Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "15 February 2023" with 100 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 25.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Goodwill Credit | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 225.0 | - | 31 January 2023 | Goodwill Credit | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 15 February 2023 | Goodwill Credit | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2845 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that simple payments are working after some of them failed with advanced payment allocation (UC4) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Customer undo "2"th transaction made on "01 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 125.0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | false | - When Customer undo "1"th transaction made on "16 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "20 January 2023" - And Customer makes "AUTOPAY" repayment on "20 January 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 0.0 | 100.0 | 25.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 100.0 | 0 | 100.0 | 400.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 40 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 31 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 15.0 | 0.0 | 15.0 | 110.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 140.0 | 0 | 140.0 | 360.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | - | 31 January 2023 | Repayment | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 360.0 | false | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 360 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 31 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 15 February 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 15 February 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 0 | 375.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 20 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 400.0 | false | - | 31 January 2023 | Repayment | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 360.0 | false | - | 15 February 2023 | Repayment | 360.0 | 360.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2846 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that Merchant issued refund with reamortization works with advanced payment allocation (UC05) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Customer undo "2"th transaction made on "01 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "08 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "08 January 2023" with 300 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 08 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.33 | 58.33 | 0.0 | 66.67 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.33 | 58.33 | 0.0 | 66.67 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 58.34 | 58.34 | 0.0 | 66.66 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 300.0 | 175.0 | 125.0 | 200.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 08 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 201 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 08 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 58.33 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 308.33 | 125.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Overpayment | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | 0.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | 0.0 | - | 08 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | 0.0 | - | 16 January 2023 | Repayment | 201.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | 1.0 | - Then Loan status has changed to "Overpaid" - - @TestRailId:C2847 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that Merchant issued refund with reamortization on due date works with advanced payment allocation (UC07) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 250.0 | 0.0 | 0.0 | 250.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - When Admin sets the business date to "16 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "16 January 2023" with 200 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 200.0 | 0.0 | 50.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 25 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 100.0 | 100.0 | 0.0 | 25.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 475.0 | 200.0 | 0.0 | 25.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | - | 31 January 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 25 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 100.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 200.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 16 January 2023 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 50.0 | - | 31 January 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | - | 15 February 2023 | Repayment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2848 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that Merchant issued refund with reamortization past due date works with advanced payment allocation (UC08) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Customer undo "2"th transaction made on "01 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 125.0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | false | - When Customer undo "1"th transaction made on "16 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "17 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 January 2023" with 300 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 300.0 | 50.0 | 250.0 | 200.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 25.0 | 25.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 50.0 | 250.0 | 100.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | - | 31 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 17 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 17 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 25.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 50.0 | 250.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 17 January 2023 | Merchant Issued Refund | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | - | 31 January 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 15 February 2023 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2849 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that full refund with CBR (UC17) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin sets the business date to "08 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "08 January 2023" with 500 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 08 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 08 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 08 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | - | 08 January 2023 | Merchant Issued Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan status has changed to "Overpaid" - When Admin sets the business date to "09 January 2023" - When Admin makes Credit Balance Refund transaction on "09 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 08 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 08 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 08 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 08 January 2023 | Merchant Issued Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 09 January 2023 | Credit Balance Refund | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2850 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that reverse-replay works with advanced payment allocation(UC24) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Customer undo "2"th transaction made on "01 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "02 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "02 January 2023" with 400 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 275.0 | 125.0 | 100.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | - When Admin sets the business date to "04 January 2023" - And Customer makes "AUTOPAY" repayment on "04 January 2023" with 50 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 108.34 | 108.34 | 0.0 | 16.66 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 325.0 | 125.0 | 50.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 125.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | false | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 0.0 | false | - | 16 January 2023 | Repayment | 125.0 | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | - Then Loan status has changed to "Overpaid" - When Admin sets the business date to "18 January 2023" - When Admin makes Credit Balance Refund transaction on "18 January 2023" with 75 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 16 January 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 500.0 | 375.0 | 125.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | false | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 0.0 | false | - | 16 January 2023 | Repayment | 125.0 | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | - | 18 January 2023 | Credit Balance Refund | 75.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | - Then Loan status has changed to "Closed (obligations met)" - When Admin sets the business date to "20 January 2023" - When Customer undo "1"th transaction made on "02 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 50.0 | 0.0 | 0.0 | 75.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 575.0 | 0 | 0.0 | 0 | 575.0 | 175.0 | 0.0 | 125.0 | 400.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | - | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | - Then Loan status has changed to "Active" - When Admin sets the business date to "31 January 2023" - And Customer makes "AUTOPAY" repayment on "31 January 2023" with 275 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 31 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 75.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 575.0 | 0 | 0.0 | 0 | 575.0 | 450.0 | 0.0 | 200.0 | 125.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | - | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | - | 31 January 2023 | Repayment | 275.0 | 275.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | false | - When Admin sets the business date to "15 February 2023" - And Customer makes "AUTOPAY" repayment on "15 February 2023" with 125 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 16 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 31 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 75.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 31 January 2023 | 125.0 | 200.0 | 0.0 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | 15 February 2023 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 575.0 | 0 | 0.0 | 0 | 575.0 | 575.0 | 0.0 | 200.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Overpayment | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | true | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 450.0 | 0.0 | false | - | 16 January 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 325.0 | 0.0 | false | - | 18 January 2023 | Credit Balance Refund | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | false | - | 31 January 2023 | Repayment | 275.0 | 275.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | false | - | 15 February 2023 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | - Then Loan status has changed to "Closed (obligations met)" - - @TestRailId:C2851 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that reamortization works with uneven balances with advanced payment allocation(UC25) - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 125.0 | 0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Customer undo "2"th transaction made on "01 January 2023" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 0.0 | 0 | 0 | 500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - When Admin sets the business date to "02 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "02 January 2023" with 400 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.67 | 91.67 | 0.0 | 33.33 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 400.0 | 275.0 | 125.0 | 100.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - When Admin sets the business date to "04 January 2023" - And Customer makes "AUTOPAY" repayment on "04 January 2023" with 50 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 108.34 | 108.34 | 0.0 | 16.66 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 91.66 | 91.66 | 0.0 | 33.34 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 450.0 | 325.0 | 125.0 | 50.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | false | - When Admin sets the business date to "06 January 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "06 January 2023" with 40 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 02 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 125.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 04 January 2023 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 06 January 2023 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 125.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 115.0 | 115.0 | 0.0 | 10.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 0.0 | 0 | 500.0 | 490.0 | 365.0 | 125.0 | 10.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | true | - | 02 January 2023 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 04 January 2023 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | false | - | 06 January 2023 | Merchant Issued Refund | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | 10.0 | false | - - @TestRailId:C2860 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: NEXT_INSTALLMENT - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2861 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment on due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2862 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment before due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "10 September 2023" - And Customer makes "AUTOPAY" repayment on "10 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - | 4 | 15 | 16 October 2023 | 10 September 2023 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 100.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 150.0 | 0.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 10 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2863 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, payment on due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 25.0 | 25.0 | 0.0 | 75.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 25.0 | 25.0 | 0.0 | 75.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 0.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 16 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2864 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, payment before due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "10 September 2023" - And Customer makes "AUTOPAY" repayment on "10 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 150.0 | 0.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 10 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2865 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: REAMORTIZATION, partial payment due date, payment before next due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 80 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 0.0 | 0.0 | 20.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 180.0 | 0.0 | 0.0 | 220.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 16 September 2023 | Repayment | 80.0 | 80.0 | 0.0 | 0.0 | 0.0 | 220.0 | - When Admin sets the business date to "20 September 2023" - And Customer makes "AUTOPAY" repayment on "20 September 2023" with 180 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 20 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 20.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 80.0 | 0.0 | 20.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 80.0 | 80.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 360.0 | 160.0 | 20.0 | 40.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 16 September 2023 | Repayment | 80.0 | 80.0 | 0.0 | 0.0 | 0.0 | 220.0 | - | 20 September 2023 | Repayment | 180.0 | 180.0 | 0.0 | 0.0 | 0.0 | 40.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2897 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, payment after due date - When Admin sets the business date to "01 September 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 September 2023 | 400 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "400" amount and expected disbursement date on "01 September 2023" - And Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 100.0 | 0.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin sets the business date to "20 September 2023" - And Customer makes "AUTOPAY" repayment on "20 September 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 300.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 20 September 2023 | 200.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 100.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 50.0 | 50.0 | 0.0 | 50.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0 | 0.0 | 0 | 400.0 | 250.0 | 50.0 | 100.0 | 150.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 01 September 2023 | Down Payment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 20 September 2023 | Repayment | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2922 - @ProgressiveLoanSchedule - @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation with progressive loan schedule with multi disbursement and with overpaid installment - When Admin sets the business date to "01 May 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 May 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 60 | DAYS | 15 | DAYS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 May 2023" with "1000" amount and expected disbursement date on "01 May 2023" - And Admin successfully disburse the loan on "01 May 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 May 2023 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 3 | 15 | 31 May 2023 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "06 May 2023" - And Customer makes "AUTOPAY" repayment on "06 May 2023" with 650 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | 3 | 15 | 31 May 2023 | 06 May 2023 | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 25.0 | 25.0 | 0.0 | 162.5 | - | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 650.0 | 400.0 | 250.0 | 350.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - When Admin sets the business date to "25 May 2023" - And Admin successfully disburse the loan on "25 May 2023" with "250" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | - | 5 | 15 | 15 June 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | - | 6 | 15 | 30 June 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1250.0 | 0 | 0.0 | 0 | 1250.0 | 650.0 | 400.0 | 250.0 | 600.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | - When Admin sets the business date to "12 June 2023" - And Admin successfully disburse the loan on "12 June 2023" with "250" EUR transaction amount - Then Loan Repayment schedule has 7 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | - | | | 12 June 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 5 | 0 | 12 June 2023 | | 687.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 6 | 15 | 15 June 2023 | | 343.75 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 25.0 | 25.0 | 0.0 | 318.75 | - | 7 | 15 | 30 June 2023 | | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | - | 12 June 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | - When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2937 - @ProgressiveLoanSchedule - @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation with progressive loan schedule with multi disbursement and reschedule - When Admin sets the business date to "01 May 2023" - And Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 May 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 60 | DAYS | 15 | DAYS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 May 2023" with "1000" amount and expected disbursement date on "01 May 2023" - And Admin successfully disburse the loan on "01 May 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 May 2023 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 3 | 15 | 31 May 2023 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "06 May 2023" - And Customer makes "AUTOPAY" repayment on "06 May 2023" with 650 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | 3 | 15 | 31 May 2023 | 06 May 2023 | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | 4 | 15 | 15 June 2023 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 25.0 | 25.0 | 0.0 | 162.5 | - | 5 | 15 | 30 June 2023 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 650.0 | 400.0 | 250.0 | 350.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - When Admin sets the business date to "25 May 2023" - When Batch API call with steps: rescheduleLoan from "15 June 2023" to "13 July 2023" submitted on date: "25 May 2023", approveReschedule on date: "25 May 2023" runs with enclosingTransaction: "true" - And Admin successfully disburse the loan on "25 May 2023" with "250" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | - | 5 | 43 | 13 July 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | - | 6 | 15 | 28 July 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1250.0 | 0 | 0.0 | 0 | 1250.0 | 650.0 | 400.0 | 250.0 | 600.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | - When Admin sets the business date to "15 July 2023" - And Admin successfully disburse the loan on "15 July 2023" with "250" EUR transaction amount - Then Loan Repayment schedule has 7 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | - | 5 | 43 | 13 July 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 25.0 | 25.0 | 0.0 | 225.0 | - | | | 15 July 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 6 | 0 | 15 July 2023 | | 437.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 7 | 15 | 28 July 2023 | | 0.0 | 437.5 | 0.0 | 0.0 | 0.0 | 437.5 | 0.0 | 0.0 | 0.0 | 437.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | - | 15 July 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | - When Batch API call with steps: rescheduleLoan from "13 July 2023" to "13 August 2023" submitted on date: "15 July 2023", approveReschedule on date: "15 July 2023" runs with enclosingTransaction: "true" - Then Loan Repayment schedule has 7 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 May 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 May 2023 | 06 May 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | - | 2 | 15 | 16 May 2023 | 06 May 2023 | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 187.5 | 187.5 | 0.0 | 0.0 | - | | | 25 May 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 25 May 2023 | | 750.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 4 | 15 | 31 May 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 187.5 | 187.5 | 0.0 | 62.5 | - | | | 15 July 2023 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 5 | 0 | 15 July 2023 | | 687.5 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | 0.0 | 0.0 | 0.0 | 62.5 | - | 6 | 74 | 13 August 2023 | | 343.75 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 25.0 | 25.0 | 0.0 | 318.75 | - | 7 | 15 | 28 August 2023 | | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | 0.0 | 0.0 | 0.0 | 343.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 0 | 0.0 | 0 | 1500.0 | 650.0 | 400.0 | 250.0 | 850.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 May 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 06 May 2023 | Repayment | 650.0 | 650.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 25 May 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 600.0 | - | 15 July 2023 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 850.0 | - When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C2940 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, loan fully paid in advance - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Loan got fully paid in advance - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1020 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | - | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2941 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, loan fully paid in advance - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Loan got fully paid in advance - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1020 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 1020.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | - | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2942 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, loan overpaid in advance - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Loan got overpaid in advance - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1120 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 100 overpaid amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 1120.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | - | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2943 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, loan overpaid in advance - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Loan got overpaid in advance - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 1120 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 0 outstanding amount - Then Loan has 100 overpaid amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 01 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 01 September 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 4 | 15 | 16 October 2023 | 01 September 2023 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 250.0 | 0.0 | 0.0 | - | 5 | 1 | 17 October 2023 | 01 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 1020.0 | 770.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 1120.0 | 1000.0 | 0.0 | 0.0 | 20.0 | 0.0 | - | 01 September 2023 | Accrual | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2944 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced repayment (future installment type: NEXT_INSTALLMENT) - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advance payment (future installment type: NEXT_INSTALLMENT) - When Admin sets the business date to "17 September 2023" - And Customer makes "AUTOPAY" repayment on "17 September 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 100.0 | 100.0 | 0.0 | 170.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 600.0 | 100.0 | 0.0 | 460.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Repayment | 100.0 | 80.0 | 0.0 | 0.0 | 20.0 | 420.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2945 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced repayment (future installment type: NEXT_INSTALLMENT) - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advance payment (future installment type: NEXT_INSTALLMENT) - When Admin sets the business date to "17 September 2023" - And Customer makes "AUTOPAY" repayment on "17 September 2023" with 100 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 60.0 | 60.0 | 0.0 | 210.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 20.0 | 20.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 600.0 | 100.0 | 0.0 | 460.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Repayment | 100.0 | 40.0 | 0.0 | 0.0 | 60.0 | 460.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2946 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount < past due charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 15 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 515.0 | 0.0 | 15.0 | 545.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - - @TestRailId:C2947 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount < past due charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 15 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 515.0 | 0.0 | 15.0 | 545.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 15.0 | 0.0 | 0.0 | 0.0 | 15.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - - @TestRailId:C2948 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > first past due charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 35.0 | 0.0 | 35.0 | 235.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 0.0 | 35.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 35.0 | 15.0 | 0.0 | 0.0 | 20.0 | 485.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2949 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > first past due charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 20.0 | 0.0 | 20.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 0.0 | 15.0 | 255.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 0.0 | 35.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2950 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > sum past due charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 350 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 17 October 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 270.0 | 0.0 | 270.0 | 0.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 80.0 | 0.0 | 80.0 | 190.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 850.0 | 0.0 | 350.0 | 210.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 350.0 | 310.0 | 0.0 | 0.0 | 40.0 | 190.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2951 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - amount > sum past due charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make late Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 October 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 October 2023" with 350 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | 17 October 2023 | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 270.0 | 0.0 | 270.0 | 0.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 80.0 | 0.0 | 80.0 | 190.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 850.0 | 0.0 | 350.0 | 210.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 October 2023 | Goodwill Credit | 350.0 | 310.0 | 0.0 | 0.0 | 40.0 | 190.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - - @TestRailId:C2952 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Goodwill credit transaction on first charge due date (future installment type: LAST_INSTALLMENT) - amount > due date charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 September 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | - | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - - @TestRailId:C2953 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Goodwill credit transaction on first charge due date (future installment type: LAST_INSTALLMENT) - amount > due date charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Admin sets the business date to "17 September 2023" - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | - | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - - @TestRailId:C2954 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Goodwill credit transaction before first charge due date (future installment type: LAST_INSTALLMENT) - amount > first and second charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - When Admin sets the business date to "17 September 2023" - # Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | - | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - - @TestRailId:C2955 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Goodwill credit transaction before first charge due date (future installment type: LAST_INSTALLMENT) - amount > first and second charge - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - When Admin sets the business date to "17 September 2023" -# Make in advanced Goodwill credit transaction (future installment type: LAST_INSTALLMENT) - When Customer makes "GOODWILL_CREDIT" transaction with "AUTOPAY" payment type on "17 September 2023" with 35 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 15.0 | 15.0 | 0.0 | 255.0 | - | 5 | 1 | 17 October 2023 | 17 September 2023 | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 20.0 | 20.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 535.0 | 35.0 | 0.0 | 525.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Goodwill Credit | 35.0 | 0.0 | 0.0 | 0.0 | 35.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 20.0 | 0.0 | 0.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 15.0 | 0.0 | 5.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - - @TestRailId:C2956 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Merchant issued refund transaction on first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) - When Admin sets the business date to "17 September 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - - @TestRailId:C2957 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Merchant issued refund transaction on first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) - When Admin sets the business date to "17 September 2023" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - - @TestRailId:C2958 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-horizontal, charge after maturity, in advanced Merchant issued refund transaction before first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - When Admin sets the business date to "17 September 2023" -# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - - @TestRailId:C2959 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, charge after maturity, in advanced Merchant issued refund transaction before first charge due date (future installment type: REAMORTIZATION) - amount < sum all charges - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# Add charge after maturity - When Admin adds "LOAN_NSF_FEE" due date charge with "17 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 September 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 0.0 | 0.0 | 0.0 | 1020.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make due date repayments - And Customer makes "AUTOPAY" repayment on "01 September 2023" with 250 EUR transaction amount - When Admin sets the business date to "16 September 2023" - And Customer makes "AUTOPAY" repayment on "16 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 500.0 | 0.0 | 0.0 | 520.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | -# Make charges for the next installments - When Admin adds "LOAN_NSF_FEE" due date charge with "17 September 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "16 October 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 0.0 | 0.0 | 0.0 | 270.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 500.0 | 0.0 | 0.0 | 560.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 0.0 | 0.0 | 20.0 | - When Admin sets the business date to "17 September 2023" -# Make in advanced Merchant issued refund transaction (future installment type: REAMORTIZATION) - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "17 September 2023" with 30 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 September 2023 | 16 September 2023 | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 01 October 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 4 | 15 | 16 October 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 20.0 | 270.0 | 10.0 | 10.0 | 0.0 | 260.0 | - | 5 | 1 | 17 October 2023 | | 0.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 10.0 | 10.0 | 0.0 | 10.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 60.0 | 1060.0 | 530.0 | 30.0 | 0.0 | 530.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 September 2023 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 16 September 2023 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 17 September 2023 | Merchant Issued Refund | 30.0 | 0.0 | 0.0 | 0.0 | 30.0 | 500.0 | - Then Loan Charges tab has the following data: - | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | - | NSF fee | true | Specified due date | 17 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 16 October 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - | NSF fee | true | Specified due date | 17 September 2023 | Flat | 20.0 | 10.0 | 0.0 | 10.0 | - - @TestRailId:C2960 @AdvancedPaymentAllocation - Scenario: Verify that rounding in case of multiple installments works properly - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1 | 01 September 2023 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 October 2023 | | 833.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | - | 2 | 31 | 01 November 2023 | | 666.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | - | 3 | 30 | 01 December 2023 | | 499.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | - | 4 | 31 | 01 January 2024 | | 332.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | - | 5 | 31 | 01 February 2024 | | 165.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | 0.0 | 0.0 | 0.0 | 167.0 | - | 6 | 29 | 01 March 2024 | | 0.0 | 165.0 | 0.0 | 0.0 | 0.0 | 165.0 | 0.0 | 0.0 | 0.0 | 165.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C2976 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify that disbursement amount only distributed only to future installments (2nd and 3rd installments) - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | - When Admin sets the business date to "01 October 2023" - When Admin successfully disburse the loan on "01 October 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | | | 01 October 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 October 2023 | | 800.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 31 | 01 November 2023 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 5 | 30 | 01 December 2023 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1400.0 | 0.0 | 50.0 | 0.0 | 1450.0 | 250.0 | 0.0 | 0.0 | 1200.0 | - - @TestRailId:C2977 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify that disbursement amount only distributed only to future installments (1st, 2nd and 3rd installments) - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | - When Admin successfully disburse the loan on "01 September 2023" with "400" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 September 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 1150.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 0 | 01 September 2023 | | 1050.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 3 | 30 | 01 October 2023 | | 700.0 | 350.0 | 0.0 | 50.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 4 | 31 | 01 November 2023 | | 350.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | - | 5 | 30 | 01 December 2023 | | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | 0.0 | 0.0 | 0.0 | 350.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1400.0 | 0.0 | 50.0 | 0.0 | 1450.0 | 250.0 | 0.0 | 0.0 | 1200.0 | - - @TestRailId:C2978 @AdvancedPaymentAllocation @ProgressiveLoanSchedule - Scenario: Verify that multiple disbursement amount only distributed only to future installments (2nd and 3rd installments) - When Admin sets the business date to "01 September 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 September 2023 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 September 2023" with "1000" amount and expected disbursement date on "01 September 2023" - When Admin successfully disburse the loan on "01 September 2023" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 September 2023" due date and 50 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "1 September 2023" with 250 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 3 | 31 | 01 November 2023 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 December 2023 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 50.0 | 0.0 | 1050.0 | 250.0 | 0.0 | 0.0 | 800.0 | - When Admin sets the business date to "01 October 2023" - When Admin successfully disburse the loan on "01 October 2023" with "400" EUR transaction amount - When Admin successfully disburse the loan on "01 October 2023" with "80" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 September 2023 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 September 2023 | 01 September 2023 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 01 October 2023 | | 500.0 | 250.0 | 0.0 | 50.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | | | 01 October 2023 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 October 2023 | | 80.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 October 2023 | | 880.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 4 | 0 | 01 October 2023 | | 860.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | - | 5 | 31 | 01 November 2023 | | 430.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | - | 6 | 30 | 01 December 2023 | | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | 0.0 | 0.0 | 0.0 | 430.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1480.0 | 0.0 | 50.0 | 0.0 | 1530.0 | 250.0 | 0.0 | 0.0 | 1280.0 | - - @TestRailId:C2986 @AdvancedPaymentAllocation - Scenario: As an admin I would like to verify that refund is working with advanced payment allocation - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - And Admin successfully disburse the loan on "01 January 2023" with "500" EUR transaction amount - Then Loan status has changed to "Active" - When Admin sets the business date to "11 January 2023" - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 January 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "21 January 2023" due date and 20 EUR transaction amount - When Admin adds "LOAN_SNOOZE_FEE" due date charge with "11 February 2023" due date and 20 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 60.0 | 0 | 560.0 | 125.0 | 0 | 0 | 435.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - When Admin sets the business date to "16 January 2023" - And Customer makes "AUTOPAY" repayment on "16 January 2023" with 315 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | 16 January 2023 | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 145.0 | 0.0 | 0.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 25.0 | 25.0 | 0.0 | 120.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 60.0 | 0 | 560.0 | 440.0 | 170 | 0 | 120.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 16 January 2023 | Repayment | 315.0 | 255.0 | 0.0 | 60.0 | 0.0 | - When Admin sets the business date to "17 January 2023" - And Admin makes "REFUND_BY_CASH" transaction with "AUTOPAY" payment type on "17 January 2023" with 150 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2023 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2023 | 01 January 2023 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2023 | 16 January 2023 | 250.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 145.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 31 January 2023 | | 125.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 20.0 | 20.0 | 0.0 | 125.0 | - | 4 | 15 | 15 February 2023 | | 0.0 | 125.0 | 0.0 | 20.0 | 0.0 | 145.0 | 0.0 | 0.0 | 0.0 | 145.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 0 | 60.0 | 0 | 560.0 | 290.0 | 20 | 0 | 270.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | - | 01 January 2023 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 01 January 2023 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 16 January 2023 | Repayment | 315.0 | 255.0 | 0.0 | 60.0 | 0.0 | - | 17 January 2023 | Refund | 150.0 | 130.0 | 0.0 | 20.0 | 0.0 | - - @TestRailId:C3042 @AdvancedPaymentAllocation - Scenario: Verify that second disbursement is working on overpaid accounts in case of NEXT_INSTALLMENT future installment allocation rule - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "500" EUR transaction amount - When Admin sets the business date to "16 January 2024" - And Customer makes "AUTOPAY" repayment on "16 January 2024" with 500 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 125 overpaid amount - When Admin successfully disburse the loan on "16 January 2024" with "500" EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 375 outstanding amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2024 | 01 January 2024 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 | - | 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2024 | Repayment | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 16 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3043 @AdvancedPaymentAllocation - Scenario: Verify that second disbursement is working on overpaid accounts in case of REAMORTIZATION future installment allocation rule - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "500" EUR transaction amount - When Admin sets the business date to "16 January 2024" - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "16 January 2024" with 500 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "OVERPAID" - Then Loan has 125 overpaid amount - When Admin successfully disburse the loan on "16 January 2024" with "500" EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 375 outstanding amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2024 | 01 January 2024 | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 | - | 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | - | 16 January 2024 | Payout Refund | 500.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 16 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 375.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3046 @AdvancedPaymentAllocation - Scenario: Verify that principal due is correct in case of second disbursement on overpaid loan - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "200" EUR transaction amount - When Admin sets the business date to "02 February 2024" - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "02 February 2024" with 200 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "OVERPAID" - Then Loan has 50 overpaid amount - When Admin successfully disburse the loan on "02 February 2024" with "160" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | 01 February 2024 | 150.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | - | | | 02 February 2024 | | 160.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 02 February 2024 | 02 February 2024 | 270.0 | 40.0 | 0.0 | 0.0 | 0.0 | 40.0 | 40.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 16 February 2024 | | 180.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.33 | 53.33 | 0.0 | 36.67 | - | 4 | 15 | 02 March 2024 | | 90.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.33 | 53.33 | 0.0 | 36.67 | - | 5 | 15 | 17 March 2024 | | 0.0 | 90.0 | 0.0 | 0.0 | 0.0 | 90.0 | 53.34 | 53.34 | 0.0 | 36.66 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 360.0 | 0 | 0.0 | 0 | 360.0 | 250.0 | 160.0 | 0 | 110.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - | 01 February 2024 | Down Payment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 02 February 2024 | Payout Refund | 200.0 | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 02 February 2024 | Disbursement | 160.0 | 0.0 | 0.0 | 0.0 | 0.0 | 110.0 | - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3049 @AdvancedPaymentAllocation - Scenario: Verify that second disbursement on overpaid loan is correct when disbursement amount is lower than overpayment amount - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "REAMORTIZATION" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "200" EUR transaction amount - When Admin sets the business date to "02 February 2024" - When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "02 February 2024" with 200 EUR transaction amount and system-generated Idempotency key - Then Loan status will be "OVERPAID" - Then Loan has 50 overpaid amount - When Admin successfully disburse the loan on "02 February 2024" with "28" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | 01 February 2024 | 150.0 | 50.0 | 0.0 | 0.0 | 0.0 | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | - | | | 02 February 2024 | | 28.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 02 February 2024 | 02 February 2024 | 171.0 | 7.0 | 0.0 | 0.0 | 0.0 | 7.0 | 7.0 | 0.0 | 0.0 | 0.0 | - | 3 | 15 | 16 February 2024 | 02 February 2024 | 114.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | - | 4 | 15 | 02 March 2024 | 02 February 2024 | 57.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | - | 5 | 15 | 17 March 2024 | 02 February 2024 | 0.0 | 57.0 | 0.0 | 0.0 | 0.0 | 57.0 | 57.0 | 57.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 228.0 | 0 | 0.0 | 0 | 228.0 | 228.0 | 171.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - | 01 February 2024 | Down Payment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 02 February 2024 | Payout Refund | 200.0 | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 02 February 2024 | Disbursement | 28.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status will be "OVERPAID" - Then Loan has 22 overpaid amount - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3068 @AdvancedPaymentAllocation - Scenario: Verify that Fraud flag can be applied on loan is its every status - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - Then Loan status will be "SUBMITTED_AND_PENDING_APPROVAL" - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - Then Loan status will be "APPROVED" - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then Loan status will be "ACTIVE" - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - When Admin sets the business date to "02 February 2024" - And Customer makes "AUTOPAY" repayment on "02 February 2024" with 100 EUR transaction amount - Then Loan status will be "ACTIVE" - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - When Admin sets the business date to "03 February 2024" - And Customer makes "AUTOPAY" repayment on "03 February 2024" with 750 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 100 overpaid amount - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - When Admin sets the business date to "04 February 2024" - When Customer undo "1"th "Repayment" transaction made on "02 February 2024" - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Admin can successfully set Fraud flag to the loan - Then Admin can successfully unset Fraud flag to the loan - - @TestRailId:C3090 - Scenario: Verify that disbursement can be done on overpaid loan in case of cummulative loan schedule - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO | 01 February 2024 | 1000 | 0 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - When Admin sets the business date to "02 February 2024" - And Customer makes "AUTOPAY" repayment on "02 February 2024" with 800 EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 50 overpaid amount - When Admin sets the business date to "03 February 2024" - When Admin successfully disburse the loan on "03 February 2024" with "20" EUR transaction amount - Then Loan status will be "OVERPAID" - Then Loan has 30 overpaid amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | - | 3 | 29 | 01 March 2024 | 02 February 2024 | 510.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | - | 4 | 31 | 01 April 2024 | 02 February 2024 | 255.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | - | 5 | 30 | 01 May 2024 | 02 February 2024 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | 255.0 | 255.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1020.0 | 0 | 0.0 | 0 | 1020.0 | 1020.0 | 770.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 02 February 2024 | Repayment | 800.0 | 770.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - When Admin sets the business date to "04 February 2024" - When Admin successfully disburse the loan on "04 February 2024" with "30" EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | - | | | 04 February 2024 | | 30.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 04 February 2024 | 02 February 2024 | 787.0 | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 8.0 | 8.0 | 0.0 | 0.0 | - | 4 | 29 | 01 March 2024 | 02 February 2024 | 524.0 | 263.0 | 0.0 | 0.0 | 0.0 | 263.0 | 263.0 | 263.0 | 0.0 | 0.0 | - | 5 | 31 | 01 April 2024 | 02 February 2024 | 261.0 | 263.0 | 0.0 | 0.0 | 0.0 | 263.0 | 263.0 | 263.0 | 0.0 | 0.0 | - | 6 | 30 | 01 May 2024 | 02 February 2024 | 0.0 | 261.0 | 0.0 | 0.0 | 0.0 | 261.0 | 261.0 | 261.0 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1050.0 | 0 | 0.0 | 0 | 1050.0 | 1050.0 | 800.0 | 0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 02 February 2024 | Repayment | 800.0 | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 04 February 2024 | Disbursement | 30.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - When Admin sets the business date to "05 February 2024" - When Admin successfully disburse the loan on "05 February 2024" with "40" EUR transaction amount - Then Loan status will be "ACTIVE" - Then Loan has 30 outstanding amount - Then Loan Repayment schedule has 7 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | 01 February 2024 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | | | 03 February 2024 | | 20.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 03 February 2024 | 02 February 2024 | 765.0 | 5.0 | 0.0 | 0.0 | 0.0 | 5.0 | 5.0 | 5.0 | 0.0 | 0.0 | - | | | 04 February 2024 | | 30.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 04 February 2024 | 02 February 2024 | 787.0 | 8.0 | 0.0 | 0.0 | 0.0 | 8.0 | 8.0 | 8.0 | 0.0 | 0.0 | - | | | 05 February 2024 | | 40.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 4 | 0 | 05 February 2024 | 02 February 2024 | 817.0 | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 10.0 | 10.0 | 0.0 | 0.0 | - | 5 | 29 | 01 March 2024 | 02 February 2024 | 544.0 | 273.0 | 0.0 | 0.0 | 0.0 | 273.0 | 273.0 | 273.0 | 0.0 | 0.0 | - | 6 | 31 | 01 April 2024 | 02 February 2024 | 271.0 | 273.0 | 0.0 | 0.0 | 0.0 | 273.0 | 273.0 | 273.0 | 0.0 | 0.0 | - | 7 | 30 | 01 May 2024 | | 0.0 | 271.0 | 0.0 | 0.0 | 0.0 | 271.0 | 241.0 | 241.0 | 0.0 | 30.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1090.0 | 0 | 0.0 | 0 | 1090.0 | 1060.0 | 810.0 | 0 | 30.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 01 February 2024 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | - | 02 February 2024 | Repayment | 800.0 | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 03 February 2024 | Disbursement | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 04 February 2024 | Disbursement | 30.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 05 February 2024 | Disbursement | 40.0 | 0.0 | 0.0 | 0.0 | 0.0 | 40.0 | - | 05 February 2024 | Down Payment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 30.0 | - - @TestRailId:C3103 - Scenario: Verify that fixed length in loan product is inherited by loan account - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 90 - - @TestRailId:C3104 - Scenario: Verify that fixed length in loan product can be overwrote upon loan account creation - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with fixed length 60 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 60 - - @TestRailId:C3119 - Scenario: Verify fixed length loan account Loan schedule - UC1: loan account with fixed length of 40 days (loanTermFrequency: 45 days) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 40 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3120 - Scenario: Verify fixed length loan account Loan schedule - UC2: loan account with fixed length of 50 days (loanTermFrequency: 45 days) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 50 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 50 - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 20 | 22 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3121 - Scenario: Verify fixed length loan account Loan schedule - UC3: loan account with fixed length of 5 weeks (loanTermFrequency: 6 weeks) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 5 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | WEEKS | 2 | WEEKS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 5 - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 14 | 15 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 14 | 29 February 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 7 | 07 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3122 - Scenario: Verify fixed length loan account Loan schedule - UC4: loan account with fixed length of 7 weeks (loanTermFrequency: 6 weeks) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 7 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | WEEKS | 2 | WEEKS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 7 - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 14 | 15 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 14 | 29 February 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 21 | 21 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3123 - Scenario: Verify fixed length loan account Loan schedule - UC5: loan account with fixed length of 5 months (loanTermFrequency: 6 months) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 5 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 2 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 5 - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 60 | 01 April 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 61 | 01 June 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 July 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3124 - Scenario: Verify fixed length loan account Loan schedule - UC6: loan account with fixed length of 7 months (loanTermFrequency: 6 months) - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 7 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 2 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then LoanDetails has fixedLength field with int value: 7 - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 60 | 01 April 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 61 | 01 June 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 92 | 01 September 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3125 - Scenario: Verify fixed length loan account Loan schedule - UC7: loan account with fixed length of 5 months but 6 month period / repayment in every 1 month results an ERROR - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Trying to create a fully customized loan with fixed length 5 and with the following data will result a 403 ERROR: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - - @TestRailId:C3126 - Scenario: Verify fixed length loan account Loan schedule - UC8: Reschedule by date a loan account with fixed length of 40 days - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 40 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "02 March 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 02 March 2024 | 02 March 2024 | 20 March 2024 | | | | | - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 33 | 20 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 10 | 30 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3127 - Scenario: Verify fixed length loan account Loan schedule - UC9: Reschedule by extra terms a loan account with fixed length of 40 days - When Admin sets the business date to "01 February 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with fixed length 40 and with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_FIXED_LENGTH | 01 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 February 2024" with "1000" amount and expected disbursement date on "01 February 2024" - When Admin successfully disburse the loan on "01 February 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 15 | 02 March 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 10 | 12 March 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "16 February 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 16 February 2024 | 16 February 2024 | | | | 2 | | - # Verify Progressive Loan reschedule behavior - future installments recalculated - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 15 | 16 February 2024 | | 600.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 3 | 15 | 02 March 2024 | | 450.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 4 | 10 | 12 March 2024 | | 300.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 5 | 20 | 01 April 2024 | | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 6 | 15 | 16 April 2024 | | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0 | 0.0 | 0 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3192 - Scenario: Verify that the error message is correct in case of the actual disbursement date is in the past with advanced payment allocation product + submitted on date repaymentStartDateType - When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "SUBMITTED_ON_DATE" - When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin sets the business date to "01 January 2023" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2023 | 500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2023" with "500" amount and expected disbursement date on "01 January 2023" - Then Loan status has changed to "Approved" - Then Admin fails to disburse the loan on "31 December 2022" with "500" EUR transaction amount because disbursement date is earlier than "01 January 2023" - When Admin sets repaymentStartDateType for "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product to "DISBURSEMENT_DATE" - - @TestRailId:C3242 - Scenario: Verify that there are no restriction on repayment reversal for overpaid loan for interest bearing product - When Admin sets the business date to "23 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 23 Jun 2024 | 400 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 30 | DAYS | 30 | DAYS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "23 June 2024" with "400" amount and expected disbursement date on "23 June 2024" - When Admin successfully disburse the loan on "23 June 2024" with "400" EUR transaction amount - And Admin sets the business date to "24 June 2024" - And Customer makes "AUTOPAY" repayment on "24 June 2024" with 100 EUR transaction amount - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 23 July 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 100.0 | 100.0 | 0.0 | 300.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 100.0 | 100.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - And Admin sets the business date to "10 September 2024" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "10 September 2024" with 400 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 10 September 2024 | Merchant Issued Refund | 400.0 | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | - Then Loan status will be "OVERPAID" - When Admin makes Credit Balance Refund transaction on "10 September 2024" with 91.21 EUR transaction amount - Then Loan Repayment schedule has 1 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 100.0 | 300.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 10 September 2024 | Merchant Issued Refund | 400.0 | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 10 September 2024 | Credit Balance Refund | 91.21 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - When Customer undo "1"th repayment on "24 June 2024" - Then Loan Repayment schedule has 2 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 June 2024 | | 400.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 23 July 2024 | 10 September 2024 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 400.0 | 0.0 | - | 2 | 49 | 10 September 2024 | | 0.0 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 491.21 | 0.0 | 0.0 | 0.0 | 491.21 | 400.0 | 0.0 | 400.0 | 91.21 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 June 2024 | Disbursement | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 24 June 2024 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 10 September 2024 | Merchant Issued Refund | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 10 September 2024 | Credit Balance Refund | 91.21 | 91.21 | 0.0 | 0.0 | 0.0 | 91.21 | - Then In Loan Transactions the "2"th Transaction has Transaction type="Repayment" and is reverted - - @TestRailId:C3282 - Scenario: Verify reversal of related interest refund transaction after merchant issued refund transactions is reversed - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount - When Admin sets the business date to "22 January 2024" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - When Customer undo "1"th "Merchant Issued Refund" transaction made on "22 January 2024" with linked "Interest Refund" transaction - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 753.25 | 246.75 | 8.39 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 2 | 29 | 01 March 2024 | | 504.02 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 253.11 | 250.91 | 4.23 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 253.11 | 2.05 | 0.0 | 0.0 | 255.16 | 0.0 | 0.0 | 0.0 | 255.16 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.58 | 0.0 | 0.0 | 1020.58 | 0.0 | 0.0 | 0.0 | 1020.58 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | true | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | true | false | - Then In Loan Transactions the "3"th Transaction has Transaction type="Interest Refund" and is reverted - - @TestRailId:C3283 - Scenario: Verify reversal of related interest refund transaction after payout refund transactions is reversed - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount - When Admin sets the business date to "22 January 2024" - When Admin makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - When Customer undo "1"th "Payout Refund" transaction made on "22 January 2024" with linked "Interest Refund" transaction - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 753.25 | 246.75 | 8.39 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 2 | 29 | 01 March 2024 | | 504.02 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 253.11 | 250.91 | 4.23 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 253.11 | 2.05 | 0.0 | 0.0 | 255.16 | 0.0 | 0.0 | 0.0 | 255.16 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.58 | 0.0 | 0.0 | 1020.58 | 0.0 | 0.0 | 0.0 | 1020.58 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | true | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | true | false | - Then In Loan Transactions the "3"th Transaction has Transaction type="Interest Refund" and is reverted - - @TestRailId:C3324 - Scenario: Verify interest refund transaction after merchant issued refund transactions is forbidden to be reversed - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount - When Admin sets the business date to "22 January 2024" - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - When Customer is forbidden to undo "1"th "Interest Refund" transaction made on "22 January 2024" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Merchant Issued Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - - @TestRailId:C3325 - Scenario: Verify interest refund transaction after payout refund transactions is forbidden to be reversed - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 01 January 2024 | 1000 | 9.9 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount - When Admin sets the business date to "22 January 2024" - When Admin makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "22 January 2024" with 100 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - When Customer is forbidden to undo "1"th "Interest Refund" transaction made on "22 January 2024" - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.97 | 247.03 | 8.11 | 0.0 | 0.0 | 255.14 | 100.57 | 100.57 | 0.0 | 154.57 | - | 2 | 29 | 01 March 2024 | | 503.74 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 252.82 | 250.92 | 4.22 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.82 | 2.05 | 0.0 | 0.0 | 254.87 | 0.0 | 0.0 | 0.0 | 254.87 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.29 | 0.0 | 0.0 | 1020.29 | 100.57 | 100.57 | 0.0 | 919.72 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 22 January 2024 | Payout Refund | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - | 22 January 2024 | Interest Refund | 0.57 | 0.57 | 0.0 | 0.0 | 0.0 | 899.43 | false | false | - - @TestRailId:C3300 - Scenario: Early pay-off loan with interest, TILL_PRECLOSE product - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - When Admin sets the business date to "01 February 2024" - And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | - When Admin sets the business date to "15 February 2024" - When Loan Pay-off is made on "15 February 2024" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 15 February 2024 | 66.8 | 16.77 | 0.24 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 15 February 2024 | 49.79 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 15 February 2024 | 32.78 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 5 | 31 | 01 June 2024 | 15 February 2024 | 15.77 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 6 | 30 | 01 July 2024 | 15 February 2024 | 0.0 | 15.77 | 0.0 | 0.0 | 0.0 | 15.77 | 15.77 | 15.77 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | - | 15 February 2024 | Repayment | 83.81 | 83.57 | 0.24 | 0.0 | 0.0 | 0.0 | false | - | 15 February 2024 | Accrual | 0.82 | 0.0 | 0.82 | 0.0 | 0.0 | 0.0 | false | - Then Loan's all installments have obligations met - - @TestRailId:C3483 - Scenario: Early pay-off loan with interest, TILL_REST_FREQUENCY_DATE product - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_REST_FREQUENCY_DATE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - When Admin sets the business date to "01 February 2024" - And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | - When Admin sets the business date to "15 February 2024" - When Loan Pay-off is made on "15 February 2024" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 15 February 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 15 February 2024 | 50.04 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 15 February 2024 | 33.03 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 5 | 31 | 01 June 2024 | 15 February 2024 | 16.02 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 6 | 30 | 01 July 2024 | 15 February 2024 | 0.0 | 16.02 | 0.0 | 0.0 | 0.0 | 16.02 | 16.02 | 16.02 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | - | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | - | 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false | - | 15 February 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | - Then Loan's all installments have obligations met - - @TestRailId:C3484 - Scenario: Interest recalculation - S1 daily for overdue loan - Given Global configuration "enable-business-date" is enabled - When Admin sets the business date to "1 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" - When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount - When Admin sets the business date to "15 July 2024" - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.71 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 34.28 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 17.85 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 17.85 | 0.58 | 0.0 | 0.0 | 18.43 | 0.0 | 0.0 | 0.0 | 18.43 | - - @TestRailId:C3485 - Scenario: Interest recalculation - S2 2 overdue - Given Global configuration "enable-business-date" is enabled - When Admin sets the business date to "1 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" - When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount - When Admin sets the business date to "10 March 2024" - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.58 | 16.56 | 0.45 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.87 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 17.06 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 17.06 | 0.1 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 | - - @TestRailId:C3486 - Scenario: Interest recalculation - S3 1 paid, 1 overdue - Given Global configuration "enable-business-date" is enabled - When Admin sets the business date to "1 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" - When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount - When Admin sets the business date to "1 February 2024" - And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount - When Admin sets the business date to "10 March 2024" - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.46 | 16.59 | 0.42 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.74 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.93 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.93 | 0.1 | 0.0 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | 17.03 | - - @TestRailId:C3487 - Scenario: Loan Details Emi Amount Variations - AssociationsAll - Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled - Given Global configuration "enable-business-date" is enabled - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with emi and the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" - And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount and "50" fixed emi amount - Then Loan emi amount variations has 1 variation, with the following data: - | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | - | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | - - @TestRailId:C3488 - Scenario: Loan Details Loan Term Variations - Given Global configuration "is-interest-to-be-recovered-first-when-greater-than-emi" is enabled - Given Global configuration "enable-business-date" is enabled - When Admin sets the business date to "1 January 2023" - When Admin creates a client with random data - When Admin creates a fully customized loan with emi and the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_INTEREST_DECLINING_BALANCE_SAR_RECALCULATION_SAME_AS_REPAYMENT_COMPOUNDING_NONE_MULTIDISB | 01 January 2023 | 10000 | 12 | DECLINING_BALANCE | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" - And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount, "50" EUR fixed emi amount and adjust repayment date on "15 January 2023" - Then Loan term variations has 2 variation, with the following data: - | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | - | 1 | loanTermType.emiAmount | emiAmount | 01 January 2023 | 50.0 | | false | | - | 4 | loanTermType.dueDate | dueDate | 01 February 2023 | 50.0 | 15 January 2023 | false | | - - @TestRailId:C3489 - Scenario: EMI calculation with 360/30 Early repayment - Last installment strategy - UC1: Multiple early repayment - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" - When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | -# --- Early repayment on 15 January 2024 --- - When Admin sets the business date to "15 January 2024" - And Customer makes "AUTOPAY" repayment on "15 January 2024" with 15.00 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.53 | 16.47 | 0.54 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 66.92 | 16.61 | 0.4 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.21 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.41 | 16.8 | 0.21 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.51 | 16.9 | 0.11 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 16.51 | 0.01 | 0.0 | 0.0 | 16.52 | 15.0 | 15.0 | 0.0 | 1.52 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 1.57 | 0.0 | 0.0 | 101.57 | 15.0 | 15.0 | 0.0 | 86.57 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | - # --- Early repayment on 15 January 2024 --- - And Customer makes "AUTOPAY" repayment on "15 January 2024" with 1.50 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.52 | 16.48 | 0.53 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 2 | 29 | 01 March 2024 | | 66.9 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 3 | 31 | 01 April 2024 | | 50.18 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.37 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 16.5 | 16.87 | 0.1 | 0.0 | 0.0 | 16.97 | 0.0 | 0.0 | 0.0 | 16.97 | - | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 1.51 | 0.0 | 0.0 | 101.51 | 16.5 | 16.5 | 0.0 | 85.01 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | - | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | - # --- Pay-off on 15 January 2024 --- - And Customer makes "AUTOPAY" repayment on "15 January 2024" with 83.76 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 15 January 2024 | 84.54 | 15.46 | 0.26 | 0.0 | 0.0 | 15.72 | 15.72 | 15.72 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 15 January 2024 | 67.53 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 15 January 2024 | 50.52 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 15 January 2024 | 33.51 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 5 | 31 | 01 June 2024 | 15 January 2024 | 16.5 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | - | 6 | 30 | 01 July 2024 | 15 January 2024 | 0.0 | 16.5 | 0.0 | 0.0 | 0.0 | 16.5 | 16.5 | 16.5 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 0.26 | 0.0 | 0.0 | 100.26 | 100.26 | 100.26 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 15 January 2024 | Repayment | 15.0 | 15.0 | 0.0 | 0.0 | 0.0 | 85.0 | - | 15 January 2024 | Repayment | 1.5 | 1.5 | 0.0 | 0.0 | 0.0 | 83.5 | - | 15 January 2024 | Repayment | 83.76 | 83.5 | 0.26 | 0.0 | 0.0 | 0.0 | - | 15 January 2024 | Accrual | 0.26 | 0.0 | 0.26 | 0.0 | 0.0 | 0.0 | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - - @TestRailId:C3424 - Scenario: Verify that after maturity date with inline COB the outstanding interest is recognized on repayment schedule - When Admin sets the business date to "23 December 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - |LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_ACTUAL | 23 December 2024 | 100 | 4 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "23 December 2024" with "100" amount and expected disbursement date on "23 December 2024" - When Admin successfully disburse the loan on "23 December 2024" with "100" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 December 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 23 January 2025 | | 66.78 | 33.22 | 0.34 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | - | 2 | 31 | 23 February 2025 | | 33.45 | 33.33 | 0.23 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | - | 3 | 28 | 23 March 2025 | | 0.0 | 33.45 | 0.1 | 0.0 | 0.0 | 33.55 | 0.0 | 0.0 | 0.0 | 33.55 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 0.67 | 0.0 | 0.0 | 100.67 | 0.0 | 0.0 | 0.0 | 100.67 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 December 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | - And Admin sets the business date to "23 March 2025" - When Admin runs inline COB job for Loan - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 23 December 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 23 January 2025 | | 66.78 | 33.22 | 0.34 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | - | 2 | 31 | 23 February 2025 | | 33.45 | 33.33 | 0.23 | 0.0 | 0.0 | 33.56 | 0.0 | 0.0 | 0.0 | 33.56 | - | 3 | 28 | 23 March 2025 | | 0.0 | 33.45 | 0.1 | 0.0 | 0.0 | 33.55 | 0.0 | 0.0 | 0.0 | 33.55 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 0.67 | 0.0 | 0.0 | 100.67 | 0.0 | 0.0 | 0.0 | 100.67 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 23 December 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | - | 22 March 2025 | Accrual | 0.67 | 0.0 | 0.67 | 0.0 | 0.0 | 0.0 | - - @TestRailId:C3534 @AdvancedPaymentAllocation - Scenario: Verify advanced payment allocation - future installments: LAST_INSTALLMENT, full Merchant issued Refund on the disbursement date, 2nd disbursement on the same date - When Admin sets the business date to "11 March 2025" - And Admin creates a client with random data - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL" loan product "MERCHANT_ISSUED_REFUND" transaction type to "LAST_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL | 11 March 2025 | 1000 | 26 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12 | MONTHS | 1 | MONTHS | 12 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "11 March 2025" with "1000" amount and expected disbursement date on "11 March 2025" - And Admin successfully disburse the loan on "11 March 2025" with "200" EUR transaction amount - Then Loan Repayment schedule has 12 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 11 April 2025 | | 185.3 | 14.7 | 4.42 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 2 | 30 | 11 May 2025 | | 170.14 | 15.16 | 3.96 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 3 | 31 | 11 June 2025 | | 154.78 | 15.36 | 3.76 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 4 | 30 | 11 July 2025 | | 138.97 | 15.81 | 3.31 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 5 | 31 | 11 August 2025 | | 122.92 | 16.05 | 3.07 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 6 | 31 | 11 September 2025 | | 106.51 | 16.41 | 2.71 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 7 | 30 | 11 October 2025 | | 89.67 | 16.84 | 2.28 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 8 | 31 | 11 November 2025 | | 72.53 | 17.14 | 1.98 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 9 | 30 | 11 December 2025 | | 54.96 | 17.57 | 1.55 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 10 | 31 | 11 January 2026 | | 37.05 | 17.91 | 1.21 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 11 | 31 | 11 February 2026 | | 18.75 | 18.3 | 0.82 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - | 12 | 28 | 11 March 2026 | | 0.0 | 18.75 | 0.37 | 0.0 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 200.0 | 29.44 | 0.0 | 0 | 229.44 | 0.0 | 0.0 | 0.0 | 229.44 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - When Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "11 March 2025" with 200 EUR transaction amount and system-generated Idempotency key - Then Loan Repayment schedule has 12 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 11 April 2025 | 11 March 2025 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 11 May 2025 | 11 March 2025 | 191.2 | 8.8 | 0.0 | 0.0 | 0.0 | 8.8 | 8.8 | 8.8 | 0.0 | 0.0 | - | 3 | 31 | 11 June 2025 | 11 March 2025 | 172.08 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 4 | 30 | 11 July 2025 | 11 March 2025 | 152.96 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 5 | 31 | 11 August 2025 | 11 March 2025 | 133.84 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 6 | 31 | 11 September 2025 | 11 March 2025 | 114.72 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 7 | 30 | 11 October 2025 | 11 March 2025 | 95.6 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 8 | 31 | 11 November 2025 | 11 March 2025 | 76.48 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 9 | 30 | 11 December 2025 | 11 March 2025 | 57.36 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 10 | 31 | 11 January 2026 | 11 March 2025 | 38.24 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 11 | 31 | 11 February 2026 | 11 March 2025 | 19.12 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - | 12 | 28 | 11 March 2026 | 11 March 2025 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 200.0 | 0 | 0.0 | 0 | 200.0 | 200.0 | 200.0 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - | 11 March 2025 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | - And Admin successfully disburse the loan on "11 March 2025" with "200" EUR transaction amount - Then Loan Repayment schedule has 12 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 11 March 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 11 April 2025 | | 366.18 | 33.82 | 4.42 | 0.0 | 0.0 | 38.24 | 0.0 | 0.0 | 0.0 | 38.24 | - | 2 | 30 | 11 May 2025 | | 331.49 | 34.69 | 3.55 | 0.0 | 0.0 | 38.24 | 8.8 | 8.8 | 0.0 | 29.44 | - | 3 | 31 | 11 June 2025 | | 296.35 | 35.14 | 3.1 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 4 | 30 | 11 July 2025 | | 260.77 | 35.58 | 2.66 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 5 | 31 | 11 August 2025 | | 224.91 | 35.86 | 2.38 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 6 | 31 | 11 September 2025 | | 188.68 | 36.23 | 2.01 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 7 | 30 | 11 October 2025 | | 152.02 | 36.66 | 1.58 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 8 | 31 | 11 November 2025 | | 115.03 | 36.99 | 1.25 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 9 | 30 | 11 December 2025 | | 77.61 | 37.42 | 0.82 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 10 | 31 | 11 January 2026 | | 39.82 | 37.79 | 0.45 | 0.0 | 0.0 | 38.24 | 19.12 | 19.12 | 0.0 | 19.12 | - | 11 | 31 | 11 February 2026 | | 19.12 | 20.7 | 0.03 | 0.0 | 0.0 | 20.73 | 19.12 | 19.12 | 0.0 | 1.61 | - | 12 | 28 | 11 March 2026 | 11 March 2025 | 0.0 | 19.12 | 0.0 | 0.0 | 0.0 | 19.12 | 19.12 | 19.12 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 400.0 | 22.25 | 0.0 | 0 | 422.25 | 200.0 | 200.0 | 0.0 | 222.25 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - | 11 March 2025 | Merchant Issued Refund | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 11 March 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_FULL" loan product "MERCHANT_ISSUED_REFUND" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3570 - Scenario: Verify Loan is fully paid and closed after full Merchant issued refund 1 day after disbursement - When Admin sets the business date to "29 March 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 28 March 2025 | 1383 | 12.23 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 24 | MONTHS | 1 | MONTHS | 24 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "28 March 2025" with "1383" amount and expected disbursement date on "28 March 2025" - When Admin successfully disburse the loan on "28 March 2025" with "1383" EUR transaction amount - Then Loan Repayment schedule has 24 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 28 March 2025 | | 1383.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 28 April 2025 | | 1331.85 | 51.15 | 14.1 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 2 | 30 | 28 May 2025 | | 1280.17 | 51.68 | 13.57 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 3 | 31 | 28 June 2025 | | 1227.97 | 52.2 | 13.05 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 4 | 30 | 28 July 2025 | | 1175.24 | 52.73 | 12.52 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 5 | 31 | 28 August 2025 | | 1121.97 | 53.27 | 11.98 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 6 | 31 | 28 September 2025 | | 1068.15 | 53.82 | 11.43 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 7 | 30 | 28 October 2025 | | 1013.79 | 54.36 | 10.89 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 8 | 31 | 28 November 2025 | | 958.87 | 54.92 | 10.33 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 9 | 30 | 28 December 2025 | | 903.39 | 55.48 | 9.77 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 10 | 31 | 28 January 2026 | | 847.35 | 56.04 | 9.21 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 11 | 31 | 28 February 2026 | | 790.74 | 56.61 | 8.64 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 12 | 28 | 28 March 2026 | | 733.55 | 57.19 | 8.06 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 13 | 31 | 28 April 2026 | | 675.78 | 57.77 | 7.48 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 14 | 30 | 28 May 2026 | | 617.42 | 58.36 | 6.89 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 15 | 31 | 28 June 2026 | | 558.46 | 58.96 | 6.29 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 16 | 30 | 28 July 2026 | | 498.9 | 59.56 | 5.69 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 17 | 31 | 28 August 2026 | | 438.73 | 60.17 | 5.08 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 18 | 31 | 28 September 2026 | | 377.95 | 60.78 | 4.47 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 19 | 30 | 28 October 2026 | | 316.55 | 61.4 | 3.85 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 20 | 31 | 28 November 2026 | | 254.53 | 62.02 | 3.23 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 21 | 30 | 28 December 2026 | | 191.87 | 62.66 | 2.59 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 22 | 31 | 28 January 2027 | | 128.58 | 63.29 | 1.96 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 23 | 31 | 28 February 2027 | | 64.64 | 63.94 | 1.31 | 0.0 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | - | 24 | 28 | 28 March 2027 | | 0.0 | 64.64 | 0.66 | 0.0 | 0.0 | 65.3 | 0.0 | 0.0 | 0.0 | 65.3 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1383.0 | 183.05 | 0.0 | 0.0 | 1566.05 | 0.0 | 0.0 | 0.0 | 1566.05 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 28 March 2025 | Disbursement | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1383.0 | false | false | - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "29 March 2025" with 1383 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 24 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 28 March 2025 | | 1383.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 28 April 2025 | 29 March 2025 | 1383.0 | 0.0 | 0.45 | 0.0 | 0.0 | 0.45 | 0.45 | 0.45 | 0.0 | 0.0 | - | 2 | 30 | 28 May 2025 | 29 March 2025 | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 28 June 2025 | 29 March 2025 | 1370.25 | 12.75 | 0.0 | 0.0 | 0.0 | 12.75 | 12.75 | 12.75 | 0.0 | 0.0 | - | 4 | 30 | 28 July 2025 | 29 March 2025 | 1305.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 5 | 31 | 28 August 2025 | 29 March 2025 | 1239.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 6 | 31 | 28 September 2025 | 29 March 2025 | 1174.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 7 | 30 | 28 October 2025 | 29 March 2025 | 1109.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 8 | 31 | 28 November 2025 | 29 March 2025 | 1044.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 9 | 30 | 28 December 2025 | 29 March 2025 | 978.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 10 | 31 | 28 January 2026 | 29 March 2025 | 913.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 11 | 31 | 28 February 2026 | 29 March 2025 | 848.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 12 | 28 | 28 March 2026 | 29 March 2025 | 783.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 13 | 31 | 28 April 2026 | 29 March 2025 | 717.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 14 | 30 | 28 May 2026 | 29 March 2025 | 652.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 15 | 31 | 28 June 2026 | 29 March 2025 | 587.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 16 | 30 | 28 July 2026 | 29 March 2025 | 522.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 17 | 31 | 28 August 2026 | 29 March 2025 | 456.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 18 | 31 | 28 September 2026 | 29 March 2025 | 391.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 19 | 30 | 28 October 2026 | 29 March 2025 | 326.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 20 | 31 | 28 November 2026 | 29 March 2025 | 261.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 21 | 30 | 28 December 2026 | 29 March 2025 | 195.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 21 | 30 | 28 December 2026 | 29 March 2025 | 195.75 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 22 | 31 | 28 January 2027 | 29 March 2025 | 130.5 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 23 | 31 | 28 February 2027 | 29 March 2025 | 65.25 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - | 24 | 28 | 28 March 2027 | 29 March 2025 | 0.0 | 65.25 | 0.0 | 0.0 | 0.0 | 65.25 | 65.25 | 65.25 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1383.0 | 0.45 | 0.0 | 0.0 | 1383.45 | 1383.45 | 1383.45 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 28 March 2025 | Disbursement | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1383.0 | false | false | - | 29 March 2025 | Merchant Issued Refund | 1383.0 | 1383.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 29 March 2025 | Interest Refund | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | - | 29 March 2025 | Accrual Activity | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | - | 29 March 2025 | Accrual | 0.45 | 0.0 | 0.45 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C3584 - Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - No interest, No interest recalculation - When Admin sets the business date to "14 March 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_DP_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 0.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" - When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | - | 3 | 15 | 13 April 2024 | | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | - | 4 | 15 | 28 April 2024 | | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 121.9 | 0.0 | 0.0 | 365.68 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - When Admin sets the business date to "24 March 2024" - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | - | 3 | 15 | 13 April 2024 | | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 79.49 | 79.49 | 0.0 | 42.4 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 323.29 | 201.39 | 0.0 | 164.29 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 164.29 outstanding amount - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 487.58 | 365.68 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" - Then Loan has 121.9 overpaid amount - When Admin sets the business date to "25 March 2024" - When Admin makes Credit Balance Refund transaction on "25 March 2024" with 121.9 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 121.9 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 121.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 0.0 | 0.0 | 0.0 | 487.58 | 487.58 | 365.68 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - When Admin sets the business date to "01 April 2024" - When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 121.89 | 121.89 | 0.0 | 117.27 | - | 5 | 15 | 28 April 2024 | | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 121.9 | 121.9 | 0.0 | 117.25 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 565.75 | 365.68 | 0.0 | 234.52 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 312.69 | false | false | - | 01 April 2024 | Down Payment | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 234.52 outstanding amount - When Admin sets the business date to "10 April 2024" - When Loan Pay-off is made on "10 April 2024" - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | 10 April 2024 | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 239.16 | 239.16 | 0.0 | 0.0 | - | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 239.15 | 239.15 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 800.27 | 600.2 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 121.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 312.69 | false | false | - | 01 April 2024 | Down Payment | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | - | 10 April 2024 | Repayment | 234.52 | 234.52 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C3585 - Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - 10% interest, No interest recalculation - When Admin sets the business date to "14 March 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_DP_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 10.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" - When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | - | 3 | 15 | 13 April 2024 | | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | - | 4 | 15 | 28 April 2024 | | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 0.0 | 0.0 | 0.0 | 122.9 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 121.9 | 0.0 | 0.0 | 368.68 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - When Admin sets the business date to "24 March 2024" - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 0.0 | 0.0 | 0.0 | 122.89 | - | 3 | 15 | 13 April 2024 | | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 78.49 | 78.49 | 0.0 | 44.4 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 323.29 | 201.39 | 0.0 | 167.29 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 167.29 outstanding amount - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 490.58 | 368.68 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" - Then Loan has 118.9 overpaid amount - When Admin sets the business date to "25 March 2024" - When Admin makes Credit Balance Refund transaction on "25 March 2024" with 118 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 122.4 | 121.89 | 1.0 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 122.4 | 0.5 | 0.0 | 0.0 | 122.9 | 122.9 | 122.9 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.0 | 0.0 | 0.0 | 490.58 | 490.58 | 368.68 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" - Then Loan has 0.9 overpaid amount - When Admin sets the business date to "01 April 2024" - When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.81 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | | 239.8 | 239.01 | 1.77 | 0.0 | 0.0 | 240.78 | 122.89 | 122.89 | 0.0 | 117.89 | - | 5 | 15 | 28 April 2024 | | 0.0 | 239.8 | 0.98 | 0.0 | 0.0 | 240.78 | 122.9 | 122.9 | 0.0 | 117.88 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 4.25 | 0.0 | 0.0 | 804.52 | 568.75 | 368.68 | 0.0 | 235.77 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 311.79 | false | false | - | 01 April 2024 | Down Payment | 77.27 | 77.27 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 235.77 outstanding amount - When Admin sets the business date to "10 April 2024" - When Loan Pay-off is made on "10 April 2024" - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 244.29 | 121.39 | 1.5 | 0.0 | 0.0 | 122.89 | 122.89 | 122.89 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 478.81 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | 10 April 2024 | 239.8 | 239.01 | 1.77 | 0.0 | 0.0 | 240.78 | 240.78 | 240.78 | 0.0 | 0.0 | - | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.8 | 0.98 | 0.0 | 0.0 | 240.78 | 240.78 | 240.78 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 4.25 | 0.0 | 0.0 | 804.52 | 804.52 | 604.45 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 199.89 | 1.5 | 0.0 | 0.0 | 165.79 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 165.79 | 1.5 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | false | false | - | 25 March 2024 | Credit Balance Refund | 118.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 311.79 | false | false | - | 01 April 2024 | Down Payment | 77.27 | 77.27 | 0.0 | 0.0 | 0.0 | 234.52 | false | false | - | 10 April 2024 | Repayment | 235.77 | 234.52 | 1.25 | 0.0 | 0.0 | 0.0 | false | false | - | 10 April 2024 | Accrual | 1.25 | 0.0 | 1.25 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C3586 - Scenario: Verify 2nd disbursement after loan was fully paid and closed (2 MIR, 1 CBR) - 33.33% interest with interest recalculation - When Admin sets the business date to "14 March 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_DP_IR_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 14 March 2024 | 1000.0 | 33.33 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "14 March 2024" with "1000.0" amount and expected disbursement date on "14 March 2024" - When Admin successfully disburse the loan on "14 March 2024" with "487.58" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 245.46 | 120.22 | 5.08 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | - | 3 | 15 | 13 April 2024 | | 123.57 | 121.89 | 3.41 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | - | 4 | 15 | 28 April 2024 | | 0.0 | 123.57 | 1.72 | 0.0 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 10.21 | 0.0 | 0.0 | 497.79 | 121.9 | 0.0 | 0.0 | 375.89 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - When Admin sets the business date to "24 March 2024" - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 201.39 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | | 244.53 | 121.15 | 4.15 | 0.0 | 0.0 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | - | 3 | 15 | 13 April 2024 | | 125.29 | 119.24 | 0.6 | 0.0 | 0.0 | 119.84 | 76.1 | 76.1 | 0.0 | 43.74 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 4.75 | 0.0 | 0.0 | 492.33 | 323.29 | 201.39 | 0.0 | 169.04 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 169.04 outstanding amount - And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "24 March 2024" with 286.19 EUR transaction amount and self-generated Idempotency key - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 125.29 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | 125.3 | 125.3 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.39 | 0.0 | 0.0 | 490.97 | 490.97 | 369.07 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" - Then Loan has 118.51 overpaid amount - When Admin makes Credit Balance Refund transaction on "24 March 2024" with 11.9 EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | - | 3 | 15 | 13 April 2024 | 24 March 2024 | 125.29 | 125.3 | 0.0 | 0.0 | 0.0 | 125.3 | 125.3 | 125.3 | 0.0 | 0.0 | - | 4 | 15 | 28 April 2024 | 24 March 2024 | 0.0 | 125.29 | 0.0 | 0.0 | 0.0 | 125.29 | 125.29 | 125.29 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 487.58 | 3.39 | 0.0 | 0.0 | 490.97 | 490.97 | 369.07 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "OVERPAID" - Then Loan has 106.61 overpaid amount - When Admin sets the business date to "01 April 2024" - When Admin successfully disburse the loan on "01 April 2024" with "312.69" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 485.11 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | | 239.25 | 245.86 | 2.29 | 0.0 | 0.0 | 248.15 | 139.52 | 139.52 | 0.0 | 108.63 | - | 5 | 15 | 28 April 2024 | | 0.0 | 239.25 | 1.39 | 0.0 | 0.0 | 240.64 | 139.51 | 139.51 | 0.0 | 101.13 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 7.07 | 0.0 | 0.0 | 807.34 | 597.58 | 397.51 | 0.0 | 209.76 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 206.08 | false | false | - Then Loan status will be "ACTIVE" - Then Loan has 209.76 outstanding amount - When Admin sets the business date to "10 April 2024" - When Loan Pay-off is made on "10 April 2024" - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 14 March 2024 | | 487.58 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 14 March 2024 | 14 March 2024 | 365.68 | 121.9 | 0.0 | 0.0 | 0.0 | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | - | 2 | 15 | 29 March 2024 | 24 March 2024 | 250.59 | 115.09 | 3.39 | 0.0 | 0.0 | 118.48 | 118.48 | 118.48 | 0.0 | 0.0 | - | | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 0 | 01 April 2024 | 01 April 2024 | 485.11 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 | - | 4 | 15 | 13 April 2024 | 10 April 2024 | 241.69 | 243.42 | 1.72 | 0.0 | 0.0 | 245.14 | 245.14 | 245.14 | 0.0 | 0.0 | - | 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 241.69 | 0.0 | 0.0 | 0.0 | 241.69 | 241.69 | 241.69 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.27 | 5.11 | 0.0 | 0.0 | 805.38 | 805.38 | 605.31 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 14 March 2024 | Disbursement | 487.58 | 0.0 | 0.0 | 0.0 | 0.0 | 487.58 | false | false | - | 14 March 2024 | Down Payment | 121.9 | 121.9 | 0.0 | 0.0 | 0.0 | 365.68 | false | false | - | 24 March 2024 | Merchant Issued Refund | 201.39 | 201.39 | 0.0 | 0.0 | 0.0 | 164.29 | false | false | - | 24 March 2024 | Merchant Issued Refund | 286.19 | 164.29 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Accrual | 3.39 | 0.0 | 3.39 | 0.0 | 0.0 | 0.0 | false | false | - | 24 March 2024 | Credit Balance Refund | 11.9 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 01 April 2024 | Disbursement | 312.69 | 0.0 | 0.0 | 0.0 | 0.0 | 206.08 | false | false | - | 10 April 2024 | Repayment | 207.8 | 206.08 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | - | 10 April 2024 | Accrual | 1.72 | 0.0 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - Then Loan has 0 outstanding amount - - @TestRailId:C3700 - Scenario: Verify repayment schedule and accrual transactions created after penalty is added for paid off loan on maturity date - UC1 - When Admin sets the business date to "08 May 2024" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_ACCRUAL_ACTIVITY | 08 May 2024 | 1000 | 12.19 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "08 May 2024" with "1000" amount and expected disbursement date on "08 May 2024" - When Admin successfully disburse the loan on "08 May 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - | 2 | 30 | 08 July 2024 | | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 0.0 | 0.0 | 0.0 | 1020.39 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - When Admin sets the business date to "08 June 2024" - And Customer makes "AUTOPAY" repayment on "08 June 2024" with 340.13 EUR transaction amount - When Admin sets the business date to "08 July 2024" - And Customer makes "AUTOPAY" repayment on "08 July 2024" with 340.13 EUR transaction amount - When Admin sets the business date to "08 August 2024" - And Admin runs inline COB job for Loan - And Customer makes "AUTOPAY" repayment on "08 August 2024" with 340.13 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2024 | 08 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 1020.39 | 0.0 | 0.0 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual Activity | 3.42 | 0.0 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | -# --- add penalty for paid off loan on maturity date ---# - When Admin adds "LOAN_NSF_FEE" due date charge with "08 August 2024" due date and 2.8 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - When Admin sets the business date to "09 August 2024" - And Admin runs inline COB job for Loan - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2024 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - When Admin sets the business date to "15 August 2024" - And Admin runs inline COB job for Loan - And Customer makes "AUTOPAY" repayment on "15 August 2024" with 2.8 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2024 | 15 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - | 15 August 2024 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - When Admin sets the business date to "16 August 2024" - And Admin runs inline COB job for Loan - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2024 | 08 June 2024 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2024 | 08 July 2024 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2024 | 15 August 2024 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2024 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2024 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2024 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2024 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2024 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2024 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2024 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - | 15 August 2024 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - - @TestRailId:C3701 - Scenario: Verify repayment schedule and accrual transactions created after penalty is added for paid off loan with accrual activity on maturity date - UC2 - When Admin sets the business date to "08 May 2025" - And Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY | 08 May 2025 | 1000 | 12.19 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "08 May 2025" with "1000" amount and expected disbursement date on "08 May 2025" - When Admin successfully disburse the loan on "08 May 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - | 2 | 30 | 08 July 2025 | | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 0.0 | 0.0 | 0.0 | 340.13 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 0.0 | 0.0 | 0.0 | 1020.39 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - When Admin sets the business date to "08 June 2025" - And Customer makes "AUTOPAY" repayment on "08 June 2025" with 340.13 EUR transaction amount - When Admin sets the business date to "08 July 2025" - And Customer makes "AUTOPAY" repayment on "08 July 2025" with 340.13 EUR transaction amount - When Admin sets the business date to "08 August 2025" - And Admin runs inline COB job for Loan - And Customer makes "AUTOPAY" repayment on "08 August 2025" with 340.13 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2025 | 08 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 0.0 | 1020.39 | 1020.39 | 0.0 | 0.0 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual Activity | 3.42 | 0.0 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | -# --- add penalty for paid off loan on maturity date ---# - When Admin adds "LOAN_NSF_FEE" due date charge with "08 August 2025" due date and 2.8 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - When Admin sets the business date to "09 August 2025" - And Admin runs inline COB job for Loan - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2025 | | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 340.13 | 0.0 | 0.0 | 2.8 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1020.39 | 0.0 | 0.0 | 2.8 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - When Admin sets the business date to "15 August 2025" - And Admin runs inline COB job for Loan - And Customer makes "AUTOPAY" repayment on "15 August 2025" with 2.8 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2025 | 15 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - When Admin sets the business date to "16 August 2025" - And Admin runs inline COB job for Loan - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 08 May 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 08 June 2025 | 08 June 2025 | 670.03 | 329.97 | 10.16 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 2 | 30 | 08 July 2025 | 08 July 2025 | 336.71 | 333.32 | 6.81 | 0.0 | 0.0 | 340.13 | 340.13 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 08 August 2025 | 15 August 2025 | 0.0 | 336.71 | 3.42 | 0.0 | 2.8 | 342.93 | 342.93 | 0.0 | 2.8 | 0.0 | - And Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.39 | 0.0 | 2.8 | 1023.19 | 1023.19 | 0.0 | 2.8 | 0.0 | - And Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 08 May 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 08 June 2025 | Repayment | 340.13 | 329.97 | 10.16 | 0.0 | 0.0 | 670.03 | false | false | - | 08 June 2025 | Accrual Activity | 10.16 | 0.0 | 10.16 | 0.0 | 0.0 | 0.0 | false | false | - | 08 July 2025 | Repayment | 340.13 | 333.32 | 6.81 | 0.0 | 0.0 | 336.71 | false | false | - | 08 July 2025 | Accrual Activity | 6.81 | 0.0 | 6.81 | 0.0 | 0.0 | 0.0 | false | false | - | 07 August 2025 | Accrual | 20.28 | 0.0 | 20.28 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Repayment | 340.13 | 336.71 | 3.42 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | - | 08 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - | 08 August 2025 | Accrual Activity | 6.22 | 0.0 | 3.42 | 0.0 | 2.8 | 0.0 | false | false | - | 15 August 2025 | Repayment | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | - - - - @TestRailId:C3963 - Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, flat interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 2 | 31 | 01 August 2024 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 01 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | - | 2 | 31 | 01 August 2024 | | 445.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | - | 3 | 31 | 01 September 2024 | | 223.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | - | 4 | 30 | 01 October 2024 | | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3964 - Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, flat interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 July 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 31 | 01 August 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 31 | 01 September 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 July 2024 | 01 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 July 2024 | | 562.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 3 | 31 | 01 August 2024 | | 375.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 4 | 31 | 01 September 2024 | | 187.5 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - | 5 | 30 | 01 October 2024 | | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | 0.0 | 0.0 | 0.0 | 187.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3965 - Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, flat interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 1000.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 2 | 31 | 01 August 2024 | | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | 0.0 | 0.0 | 0.0 | 500.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 July 2024 | 01 July 2024 | | | | 2 | | - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 1200.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 2 | 31 | 01 August 2024 | | 900.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 3 | 31 | 01 September 2024 | | 600.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 4 | 30 | 01 October 2024 | | 300.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - | 5 | 31 | 01 November 2024 | | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | 0.0 | 0.0 | 0.0 | 300.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - - @TestRailId:C3966 - Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, flat interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - When Admin sets the business date to "01 July 2024" - And Customer makes "AUTOPAY" repayment on "01 July 2024" with 400 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | - When Admin sets the business date to "15 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 15 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 533.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | - | 3 | 31 | 01 September 2024 | | 266.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | - | 4 | 30 | 01 October 2024 | | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | - - @TestRailId:C3967 - Scenario: Verify Progressive Loan reschedule by extending repayment periods after partial repayment and then backdated repayment occurs, flat interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - When Admin sets the business date to "01 July 2024" - And Customer makes "AUTOPAY" repayment on "01 July 2024" with 400 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | - When Admin sets the business date to "15 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 September 2024 | 15 July 2024 | | | | 2 | | - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | - | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | - | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | - | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | - When Admin sets the business date to "15 October 2024" - And Customer makes "AUTOPAY" repayment on "01 August 2024" with 500 EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | 01 August 2024 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | - | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | - | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 100.0 | 100.0 | 0.0 | 34.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 900.0 | 100.0 | 0.0 | 300.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | - | 01 August 2024 | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 300.0 | - When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - - @TestRailId:C3987 - Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, declining balance interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | - | 2 | 31 | 01 August 2024 | | 336.66 | 333.32 | 6.7 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | - | 3 | 31 | 01 September 2024 | | 0.0 | 336.66 | 3.37 | 0.0 | 0.0 | 340.03 | 0.0 | 0.0 | 0.0 | 340.03 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.07 | 0.0 | 0.0 | 1020.07 | 0.0 | 0.0 | 0.0 | 1020.07 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 01 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | - | 2 | 31 | 01 August 2024 | | 448.87 | 221.11 | 6.7 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | - | 3 | 31 | 01 September 2024 | | 225.55 | 223.32 | 4.49 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | - | 4 | 30 | 01 October 2024 | | 0.0 | 225.55 | 2.26 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 23.45 | 0.0 | 0.0 | 1023.45 | 0.0 | 0.0 | 0.0 | 1023.45 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3989 - Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, declining balance interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_DOWNPAYMENT_INTEREST | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 July 2024 | | 502.4 | 247.6 | 7.4 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | - | 3 | 31 | 01 August 2024 | | 252.52 | 249.88 | 5.12 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | - | 4 | 31 | 01 September 2024 | | 0.0 | 252.52 | 2.57 | 0.0 | 0.0 | 255.09 | 0.0 | 0.0 | 0.0 | 255.09 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 15.09 | 0.0 | 0.0 | 1015.09 | 0.0 | 0.0 | 0.0 | 1015.09 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 July 2024 | 01 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 30 | 01 July 2024 | | 629.4 | 120.6 | 7.4 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | - | 3 | 31 | 01 August 2024 | | 507.81 | 121.59 | 6.41 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | - | 4 | 31 | 01 September 2024 | | 384.99 | 122.82 | 5.18 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | - | 5 | 30 | 01 October 2024 | | 0.0 | 384.99 | 3.8 | 0.0 | 0.0 | 388.79 | 0.0 | 0.0 | 0.0 | 388.79 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 22.79 | 0.0 | 0.0 | 1022.79 | 0.0 | 0.0 | 0.0 | 1022.79 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - - @TestRailId:C3990 - Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, declining balance interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 1004.97 | 495.03 | 15.0 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | - | 2 | 31 | 01 August 2024 | | 504.99 | 499.98 | 10.05 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | - | 3 | 31 | 01 September 2024 | | 0.0 | 504.99 | 5.05 | 0.0 | 0.0 | 510.04 | 0.0 | 0.0 | 0.0 | 510.04 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 30.1 | 0.0 | 0.0 | 1530.1 | 0.0 | 0.0 | 0.0 | 1530.1 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - When Admin sets the business date to "01 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 July 2024 | 01 July 2024 | | | | 2 | | - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 1205.94 | 294.06 | 15.0 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | - | 2 | 31 | 01 August 2024 | | 908.94 | 297.0 | 12.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | - | 3 | 31 | 01 September 2024 | | 608.97 | 299.97 | 9.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | - | 4 | 30 | 01 October 2024 | | 306.0 | 302.97 | 6.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | - | 5 | 31 | 01 November 2024 | | 0.0 | 306.0 | 3.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 45.3 | 0.0 | 0.0 | 1545.3 | 0.0 | 0.0 | 0.0 | 1545.3 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | - - @TestRailId:C3994 - Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, declining balance interest type - When Admin sets the business date to "01 June 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" - When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | - | 2 | 31 | 01 August 2024 | | 403.98 | 399.99 | 8.04 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | - | 3 | 31 | 01 September 2024 | | 0.0 | 403.98 | 4.04 | 0.0 | 0.0 | 408.02 | 0.0 | 0.0 | 0.0 | 408.02 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 24.08 | 0.0 | 0.0 | 1224.08 | 0.0 | 0.0 | 0.0 | 1224.08 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - When Admin sets the business date to "01 July 2024" - And Customer makes "AUTOPAY" repayment on "01 July 2024" with 417.03 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 403.89 | 400.08 | 7.95 | 0.0 | 0.0 | 408.03 | 9.0 | 9.0 | 0.0 | 399.03 | - | 3 | 31 | 01 September 2024 | | 0.0 | 403.89 | 4.04 | 0.0 | 0.0 | 407.93 | 0.0 | 0.0 | 0.0 | 407.93 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 23.99 | 0.0 | 0.0 | 1223.99 | 417.03 | 9.0 | 0.0 | 806.96 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | - When Admin sets the business date to "15 July 2024" - When Admin creates and approves Loan reschedule with the following data: - | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 15 July 2024 | | | | 1 | | - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 538.58 | 265.39 | 7.95 | 0.0 | 0.0 | 273.34 | 9.0 | 9.0 | 0.0 | 264.34 | - | 3 | 31 | 01 September 2024 | | 270.63 | 267.95 | 5.39 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | - | 4 | 30 | 01 October 2024 | | 0.0 | 270.63 | 2.71 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1200.0 | 28.05 | 0.0 | 0.0 | 1228.05 | 417.03 | 9.0 | 0.0 | 811.02 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | - | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | - | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | - - @TestRailId:C4028 - Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with exact disb amount in expected order - UC1 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 200.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 700.0 | | - | 01 January 2025 | | 200.0 | | -# --- 1st disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | | 200.0 | | -# --- 2nd disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | 01 January 2025 | 200.0 | | - - When Loan Pay-off is made on "01 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4029 - Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with over expected disb amount in expected order - UC2 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 200.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 700.0 | | - | 01 January 2025 | | 200.0 | | -# --- 1st disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "750" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 626.81 | 123.19 | 4.37 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | - | 2 | 28 | 01 March 2025 | | 502.91 | 123.9 | 3.66 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | - | 3 | 31 | 01 April 2025 | | 378.28 | 124.63 | 2.93 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | - | 4 | 30 | 01 May 2025 | | 252.93 | 125.35 | 2.21 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | - | 5 | 31 | 01 June 2025 | | 126.85 | 126.08 | 1.48 | 0.0 | 0.0 | 127.56 | 0.0 | 0.0 | 0.0 | 127.56 | - | 6 | 30 | 01 July 2025 | | 0.0 | 126.85 | 0.74 | 0.0 | 0.0 | 127.59 | 0.0 | 0.0 | 0.0 | 127.59 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 750.0 | 15.39 | 0.0 | 0.0 | 765.39 | 0.0 | 0.0 | 0.0 | 765.39 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 750.0 | | - | 01 January 2025 | | 200.0 | | -# --- 2nd disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "250" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 2 | 28 | 01 March 2025 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 3 | 31 | 01 April 2025 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 4 | 30 | 01 May 2025 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 5 | 31 | 01 June 2025 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 6 | 30 | 01 July 2025 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | - | 01 January 2025 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 750.0 | | - | 01 January 2025 | 01 January 2025 | 250.0 | | - - When Loan Pay-off is made on "1 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4030 - Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with over expected disb amount in not expected order - UC3 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 200.0 | | - | 01 January 2025 | | 500.0 | | -# --- 1st disbursement - 1 January, 2025 --- - Then Admin fails to disburse the loan on "1 January 2025" with "801" EUR transaction amount due to exceed approved amount - When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 250.72 | 49.28 | 1.75 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | - | 2 | 28 | 01 March 2025 | | 201.15 | 49.57 | 1.46 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | - | 3 | 31 | 01 April 2025 | | 151.29 | 49.86 | 1.17 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | - | 4 | 30 | 01 May 2025 | | 101.14 | 50.15 | 0.88 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | - | 5 | 31 | 01 June 2025 | | 50.7 | 50.44 | 0.59 | 0.0 | 0.0 | 51.03 | 0.0 | 0.0 | 0.0 | 51.03 | - | 6 | 30 | 01 July 2025 | | 0.0 | 50.7 | 0.3 | 0.0 | 0.0 | 51.0 | 0.0 | 0.0 | 0.0 | 51.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 300.0 | 6.15 | 0.0 | 0.0 | 306.15 | 0.0 | 0.0 | 0.0 | 306.15 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 300.0 | | - | 01 January 2025 | | 200.0 | | -# --- 2nd disbursement - 1 January, 2025 --- - Then Admin fails to disburse the loan on "1 January 2025" with "701" EUR transaction amount due to exceed approved amount - When Admin successfully disburse the loan on "01 January 2025" with "600" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 600.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - | 01 January 2025 | Disbursement | 600.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 300.0 | | - | 01 January 2025 | 01 January 2025 | 600.0 | | - - When Loan Pay-off is made on "01 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4031 - Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date with diff expected disb amounts in diff order - UC4 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 700.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 200.0 | | - | 01 January 2025 | | 700.0 | | -# --- 1st disbursement - 1 January, 2025 --- - Then Admin fails to disburse the loan on "1 January 2025" with "900" EUR transaction amount due to exceed approved amount - When Admin successfully disburse the loan on "01 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 417.88 | 82.12 | 2.92 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | - | 2 | 28 | 01 March 2025 | | 335.28 | 82.6 | 2.44 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | - | 3 | 31 | 01 April 2025 | | 252.2 | 83.08 | 1.96 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | - | 4 | 30 | 01 May 2025 | | 168.63 | 83.57 | 1.47 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | - | 5 | 31 | 01 June 2025 | | 84.57 | 84.06 | 0.98 | 0.0 | 0.0 | 85.04 | 0.0 | 0.0 | 0.0 | 85.04 | - | 6 | 30 | 01 July 2025 | | 0.0 | 84.57 | 0.49 | 0.0 | 0.0 | 85.06 | 0.0 | 0.0 | 0.0 | 85.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 500.0 | 10.26 | 0.0 | 0.0 | 510.26 | 0.0 | 0.0 | 0.0 | 510.26 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 500.0 | | - | 01 January 2025 | | 200.0 | | -# --- 2nd disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 668.6 | 131.4 | 4.67 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | - | 2 | 28 | 01 March 2025 | | 536.43 | 132.17 | 3.9 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | - | 3 | 31 | 01 April 2025 | | 403.49 | 132.94 | 3.13 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | - | 4 | 30 | 01 May 2025 | | 269.77 | 133.72 | 2.35 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | - | 5 | 31 | 01 June 2025 | | 135.27 | 134.5 | 1.57 | 0.0 | 0.0 | 136.07 | 0.0 | 0.0 | 0.0 | 136.07 | - | 6 | 30 | 01 July 2025 | | 0.0 | 135.27 | 0.79 | 0.0 | 0.0 | 136.06 | 0.0 | 0.0 | 0.0 | 136.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.0 | 16.41 | 0.0 | 0.0 | 816.41 | 0.0 | 0.0 | 0.0 | 816.41 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | false | false | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 500.0 | | - | 01 January 2025 | 01 January 2025 | 300.0 | | - - When Loan Pay-off is made on "1 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4032 - Scenario: Verify tranche interest bearing progressive loan that expects two tranches at the same date in defined order with over expected 2nd disb amount - UC5 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date | 1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 200.0 | 01 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 200.0 | | - | 01 January 2025 | | 500.0 | | -# --- 1st disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 167.15 | 32.85 | 1.17 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | - | 2 | 28 | 01 March 2025 | | 134.11 | 33.04 | 0.98 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | - | 3 | 31 | 01 April 2025 | | 100.87 | 33.24 | 0.78 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | - | 4 | 30 | 01 May 2025 | | 67.44 | 33.43 | 0.59 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | - | 5 | 31 | 01 June 2025 | | 33.81 | 33.63 | 0.39 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 | - | 6 | 30 | 01 July 2025 | | 0.0 | 33.81 | 0.2 | 0.0 | 0.0 | 34.01 | 0.0 | 0.0 | 0.0 | 34.01 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 200.0 | 4.11 | 0.0 | 0.0 | 204.11 | 0.0 | 0.0 | 0.0 | 204.11 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 200.0 | | - | 01 January 2025 | | 500.0 | | -# --- 2nd disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "800" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 800.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 835.74 | 164.26 | 5.83 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 2 | 28 | 01 March 2025 | | 670.53 | 165.21 | 4.88 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 3 | 31 | 01 April 2025 | | 504.35 | 166.18 | 3.91 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 4 | 30 | 01 May 2025 | | 337.2 | 167.15 | 2.94 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 5 | 31 | 01 June 2025 | | 169.08 | 168.12 | 1.97 | 0.0 | 0.0 | 170.09 | 0.0 | 0.0 | 0.0 | 170.09 | - | 6 | 30 | 01 July 2025 | | 0.0 | 169.08 | 0.99 | 0.0 | 0.0 | 170.07 | 0.0 | 0.0 | 0.0 | 170.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 20.52 | 0.0 | 0.0 | 1020.52 | 0.0 | 0.0 | 0.0 | 1020.52 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 200.0 | false | false | - | 01 January 2025 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 200.0 | | - | 01 January 2025 | 01 January 2025 | 800.0 | | - - When Loan Pay-off is made on "1 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4033 - Scenario: Verify tranche interest bearing progressive loan that expects tranche with added 2nd tranche at the same date and undo disbursement - UC6 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursement details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - And Admin successfully add disbursement detail to the loan on "01 January 2025" with 300 EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | | 300.0 | 700.0 | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 585.02 | 114.98 | 4.08 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 2 | 28 | 01 March 2025 | | 469.37 | 115.65 | 3.41 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 3 | 31 | 01 April 2025 | | 353.05 | 116.32 | 2.74 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 4 | 30 | 01 May 2025 | | 236.05 | 117.0 | 2.06 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 5 | 31 | 01 June 2025 | | 118.37 | 117.68 | 1.38 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - | 6 | 30 | 01 July 2025 | | 0.0 | 118.37 | 0.69 | 0.0 | 0.0 | 119.06 | 0.0 | 0.0 | 0.0 | 119.06 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 14.36 | 0.0 | 0.0 | 714.36 | 0.0 | 0.0 | 0.0 | 714.36 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | -# --- 2nd disbursement - 1 Jan, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | 01 January 2025 | 200.0 | 700.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false | false | - Then Admin fails to disburse the loan on "01 January 2025" with "100" amount -# -- undo disbursement ---- - When Admin successfully undo disbursal - Then Loan status has changed to "Approved" - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 700.0 | | - | 01 January 2025 | | 200.0 | 700.0 | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 752.17 | 147.83 | 5.25 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 2 | 28 | 01 March 2025 | | 603.48 | 148.69 | 4.39 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 3 | 31 | 01 April 2025 | | 453.92 | 149.56 | 3.52 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 4 | 30 | 01 May 2025 | | 303.49 | 150.43 | 2.65 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 5 | 31 | 01 June 2025 | | 152.18 | 151.31 | 1.77 | 0.0 | 0.0 | 153.08 | 0.0 | 0.0 | 0.0 | 153.08 | - | 6 | 30 | 01 July 2025 | | 0.0 | 152.18 | 0.89 | 0.0 | 0.0 | 153.07 | 0.0 | 0.0 | 0.0 | 153.07 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 900.0 | 18.47 | 0.0 | 0.0 | 918.47 | 0.0 | 0.0 | 0.0 | 918.47 | - Then Loan Transactions tab has none transaction -#---- make two disbursements on Jan1 , 2025 ---# - When Admin successfully disburse the loan on "01 January 2025" with "750" EUR transaction amount - When Admin successfully disburse the loan on "01 January 2025" with "200" EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 750.0 | | - | 01 January 2025 | 01 January 2025 | 200.0 | 700.0 | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 793.96 | 156.04 | 5.54 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | - | 2 | 28 | 01 March 2025 | | 637.01 | 156.95 | 4.63 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | - | 3 | 31 | 01 April 2025 | | 479.15 | 157.86 | 3.72 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | - | 4 | 30 | 01 May 2025 | | 320.37 | 158.78 | 2.8 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | - | 5 | 31 | 01 June 2025 | | 160.66 | 159.71 | 1.87 | 0.0 | 0.0 | 161.58 | 0.0 | 0.0 | 0.0 | 161.58 | - | 6 | 30 | 01 July 2025 | | 0.0 | 160.66 | 0.94 | 0.0 | 0.0 | 161.6 | 0.0 | 0.0 | 0.0 | 161.6 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 950.0 | 19.5 | 0.0 | 0.0 | 969.5 | 0.0 | 0.0 | 0.0 | 969.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | - | 01 January 2025 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 950.0 | false | false | - When Admin sets the business date to "01 February 2025" - When Admin runs inline COB job for Loan - Then Admin fails to disburse the loan on "01 February 2025" with "50" amount - - When Loan Pay-off is made on "1 February 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4034 - Scenario: Verify tranche interest bearing progressive loan that expects tranches at the same date with repayment and undo last disbursement - UC7 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date | 2nd_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 90 | DAYS | 15 | DAYS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 700.0 | 01 January 2025 | 300.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 15 | 16 January 2025 | | 834.55 | 165.45 | 2.92 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 2 | 15 | 31 January 2025 | | 668.61 | 165.94 | 2.43 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 3 | 15 | 15 February 2025 | | 502.19 | 166.42 | 1.95 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 4 | 15 | 02 March 2025 | | 335.28 | 166.91 | 1.46 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 5 | 15 | 17 March 2025 | | 167.89 | 167.39 | 0.98 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 6 | 15 | 01 April 2025 | | 0.0 | 167.89 | 0.49 | 0.0 | 0.0 | 168.38 | 0.0 | 0.0 | 0.0 | 168.38 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 10.23 | 0.0 | 0.0 | 1010.23 | 0.0 | 0.0 | 0.0 | 1010.23 | -# --- 1st disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "700" EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | | 300.0 | | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | | 584.18 | 115.82 | 2.04 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 2 | 15 | 31 January 2025 | | 468.02 | 116.16 | 1.7 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 3 | 15 | 15 February 2025 | | 351.53 | 116.49 | 1.37 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 4 | 15 | 02 March 2025 | | 234.7 | 116.83 | 1.03 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 5 | 15 | 17 March 2025 | | 117.52 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 6 | 15 | 01 April 2025 | | 0.0 | 117.52 | 0.34 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 7.16 | 0.0 | 0.0 | 707.16 | 0.0 | 0.0 | 0.0 | 707.16 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | -# --- 1st repayment - 1 January, 2025 --- - And Customer makes "AUTOPAY" repayment on "01 January 2025" with 117.86 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | 01 January 2025 | 582.14 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | 117.86 | 117.86 | 0.0 | 0.0 | - | 2 | 15 | 31 January 2025 | | 467.68 | 114.46 | 3.4 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 3 | 15 | 15 February 2025 | | 351.18 | 116.5 | 1.36 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 4 | 15 | 02 March 2025 | | 234.34 | 116.84 | 1.02 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 5 | 15 | 17 March 2025 | | 117.16 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 6 | 15 | 01 April 2025 | | 0.0 | 117.16 | 0.34 | 0.0 | 0.0 | 117.5 | 0.0 | 0.0 | 0.0 | 117.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 6.8 | 0.0 | 0.0 | 706.8 | 117.86 | 117.86 | 0.0 | 588.94 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | -# --- 2nd disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - | 01 January 2025 | 01 January 2025 | 300.0 | | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | | 834.2 | 165.8 | 2.57 | 0.0 | 0.0 | 168.37 | 117.86 | 117.86 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 668.26 | 165.94 | 2.43 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 3 | 15 | 15 February 2025 | | 501.84 | 166.42 | 1.95 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 4 | 15 | 02 March 2025 | | 334.93 | 166.91 | 1.46 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 5 | 15 | 17 March 2025 | | 167.54 | 167.39 | 0.98 | 0.0 | 0.0 | 168.37 | 0.0 | 0.0 | 0.0 | 168.37 | - | 6 | 15 | 01 April 2025 | | 0.0 | 167.54 | 0.49 | 0.0 | 0.0 | 168.03 | 0.0 | 0.0 | 0.0 | 168.03 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 9.88 | 0.0 | 0.0 | 1009.88 | 117.86 | 117.86 | 0.0 | 892.02 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 882.14 | false | false | - Then Admin fails to disburse the loan on "01 January 2025" with "100" amount -# --- undo disbursement --- # - When Admin successfully undo last disbursal - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 700.0 | | - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 700.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | 01 January 2025 | 582.14 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | 117.86 | 117.86 | 0.0 | 0.0 | - | 2 | 15 | 31 January 2025 | | 467.68 | 114.46 | 3.4 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 3 | 15 | 15 February 2025 | | 351.18 | 116.5 | 1.36 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 4 | 15 | 02 March 2025 | | 234.34 | 116.84 | 1.02 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 5 | 15 | 17 March 2025 | | 117.16 | 117.18 | 0.68 | 0.0 | 0.0 | 117.86 | 0.0 | 0.0 | 0.0 | 117.86 | - | 6 | 15 | 01 April 2025 | | 0.0 | 117.16 | 0.34 | 0.0 | 0.0 | 117.5 | 0.0 | 0.0 | 0.0 | 117.5 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 700.0 | 6.8 | 0.0 | 0.0 | 706.8 | 117.86 | 117.86 | 0.0 | 588.94 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 700.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | - | 01 January 2025 | Repayment | 117.86 | 117.86 | 0.0 | 0.0 | 0.0 | 582.14 | false | false | - Then Admin fails to disburse the loan on "01 January 2025" with "200" amount - - When Loan Pay-off is made on "1 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4035 - Scenario: Verify tranche interest bearing progressive loan that expects tranche with added 2 tranches at the same date - UC8 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursement details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_EXPECT_TRANCHE | 01 January 2025 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 90 | DAYS | 15 | DAYS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | 01 January 2025 | 300.0 | - And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 3 | 15 | 15 February 2025 | | 150.67 | 49.92 | 0.59 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 4 | 15 | 02 March 2025 | | 100.6 | 50.07 | 0.44 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 5 | 15 | 17 March 2025 | | 50.38 | 50.22 | 0.29 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 6 | 15 | 01 April 2025 | | 0.0 | 50.38 | 0.15 | 0.0 | 0.0 | 50.53 | 0.0 | 0.0 | 0.0 | 50.53 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 300.0 | 3.08 | 0.0 | 0.0 | 303.08 | 0.0 | 0.0 | 0.0 | 303.08 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 300.0 | | - And Admin successfully add disbursement detail to the loan on "01 February 2025" with 500 EUR transaction amount - And Admin successfully add disbursement detail to the loan on "01 February 2025" with 200 EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 3 | 15 | 15 February 2025 | | 676.32 | 224.27 | 2.49 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | - | 4 | 15 | 02 March 2025 | | 451.53 | 224.79 | 1.97 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | - | 5 | 15 | 17 March 2025 | | 226.09 | 225.44 | 1.32 | 0.0 | 0.0 | 226.76 | 0.0 | 0.0 | 0.0 | 226.76 | - | 6 | 15 | 01 April 2025 | | 0.0 | 226.09 | 0.66 | 0.0 | 0.0 | 226.75 | 0.0 | 0.0 | 0.0 | 226.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.00 | 8.05 | 0.0 | 0.0 | 1008.05 | 0.0 | 0.0 | 0.0 | 1008.05 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | | 300.0 | | - | 01 February 2025 | | 500.0 | 1000.0 | - | 01 February 2025 | | 200.0 | 1000.0 | -# --- 1st disbursement - 1 January, 2025 --- - When Admin successfully disburse the loan on "01 January 2025" with "300" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 200.59 | 49.78 | 0.73 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 01 February 2025 | | 200.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 3 | 15 | 15 February 2025 | | 850.67 | 49.92 | 0.59 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 4 | 15 | 02 March 2025 | | 800.6 | 50.07 | 0.44 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 5 | 15 | 17 March 2025 | | 750.38 | 50.22 | 0.29 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 6 | 15 | 01 April 2025 | | 700.0 | 50.38 | 0.15 | 0.0 | 0.0 | 50.53 | 0.0 | 0.0 | 0.0 | 50.53 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 300.0 | 3.08 | 0.0 | 0.0 | 303.08 | 0.0 | 0.0 | 0.0 | 303.08 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 300.0 | | - | 01 February 2025 | | 500.0 | 1000.0 | - | 01 February 2025 | | 200.0 | 1000.0 | -# --- 2nd disbursement - 1 February, 2025 --- - When Admin sets the business date to "01 February 2025" - When Admin runs inline COB job for Loan - When Admin successfully disburse the loan on "01 February 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 200.74 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 15 | 15 February 2025 | | 526.31 | 174.43 | 1.97 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | - | 4 | 15 | 02 March 2025 | | 351.45 | 174.86 | 1.54 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | - | 5 | 15 | 17 March 2025 | | 176.08 | 175.37 | 1.03 | 0.0 | 0.0 | 176.4 | 0.0 | 0.0 | 0.0 | 176.4 | - | 6 | 15 | 01 April 2025 | | 0.0 | 176.08 | 0.51 | 0.0 | 0.0 | 176.59 | 0.0 | 0.0 | 0.0 | 176.59 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 800.0 | 6.81 | 0.0 | 0.0 | 806.81 | 0.0 | 0.0 | 0.0 | 806.81 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 300.0 | | - | 01 February 2025 | 01 February 2025 | 500.0 | 1000.0 | - | 01 February 2025 | | 200.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - | 31 January 2025 | Accrual | 1.76 | 0.0 | 1.76 | 0.0 | 0.0 | 0.0 | false | false | - | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | - -# --- 3rd disbursement - 1 February, 2025 --- - When Admin successfully disburse the loan on "01 February 2025" with "150" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 300.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2025 | | 250.37 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | 2 | 15 | 31 January 2025 | | 200.74 | 49.63 | 0.88 | 0.0 | 0.0 | 50.51 | 0.0 | 0.0 | 0.0 | 50.51 | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 February 2025 | | 150.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 3 | 15 | 15 February 2025 | | 638.95 | 211.79 | 2.37 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | - | 4 | 15 | 02 March 2025 | | 426.65 | 212.3 | 1.86 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | - | 5 | 15 | 17 March 2025 | | 213.73 | 212.92 | 1.24 | 0.0 | 0.0 | 214.16 | 0.0 | 0.0 | 0.0 | 214.16 | - | 6 | 15 | 01 April 2025 | | 0.0 | 213.73 | 0.62 | 0.0 | 0.0 | 214.35 | 0.0 | 0.0 | 0.0 | 214.35 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 950.0 | 7.85 | 0.0 | 0.0 | 957.85 | 0.0 | 0.0 | 0.0 | 957.85 | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 300.0 | | - | 01 February 2025 | 01 February 2025 | 500.0 | 1000.0 | - | 01 February 2025 | 01 February 2025 | 150.0 | 1000.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 300.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - | 31 January 2025 | Accrual | 1.76 | 0.0 | 1.76 | 0.0 | 0.0 | 0.0 | false | false | - | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | false | false | - | 01 February 2025 | Disbursement | 150.0 | 0.0 | 0.0 | 0.0 | 0.0 | 950.0 | false | false | - Then Admin fails to disburse the loan on "01 February 2025" with "50" amount - - When Loan Pay-off is made on "1 February 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4118 - Scenario: Verify cumulative multidisb loan with 2nd disb at 1st installment with flat interest type and same_as_repeyment interest calculation period - UC1 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | -# -- 2nd disb - on Jan, 15, 2025 --# - When Admin sets the business date to "15 January 2025" - When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | -# --- undo last disbursement --- # - When Admin successfully undo last disbursal - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - - When Loan Pay-off is made on "15 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4119 - Scenario: Verify cumulative multidisbursal loan with 2nd disb at 2nd installment with flat interest type and same_as_repeyment interest calculation period - UC2 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | -# -- 2nd disb - on Feb, 15, 2025 --# - When Admin sets the business date to "15 February 2025" - When Admin successfully disburse the loan on "15 February 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 8.75 | 0.0 | 0.0 | 342.08 | 0.0 | 0.0 | 0.0 | 342.08 | - | | | 15 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 28 | 01 March 2025 | | 666.67 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 666.67 | 8.75 | 0.0 | 0.0 | 675.42 | 0.0 | 0.0 | 0.0 | 675.42 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 15 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | -# --- undo disbursement --- # - When Admin successfully undo disbursal - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | - Then Loan Transactions tab has none transaction - - Then Admin can successfully undone the loan approval - Then Loan status will be "SUBMITTED_AND_PENDING_APPROVAL" - And Admin successfully rejects the loan on "15 February 2025" - Then Loan status will be "REJECTED" - - @TestRailId:C4120 - Scenario: Verify cumulative multidisbursal loan with repayment between disbursements with flat interest type and same_as_repeyment interest calculation period - UC3 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_ACTUAL_ACTUAL_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | -# -- repayment on Feb, 1, 2025 ---# - When Admin sets the business date to "01 February 2025" - And Customer makes "AUTOPAY" repayment on "01 February 2025" with 339.16 EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | 01 February 2025 | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 339.16 | 0.0 | 0.0 | 0.0 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 339.16 | 0.0 | 0.0 | 678.34 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2025 | Repayment | 339.16 | 333.33 | 5.83 | 0.0 | 0.0 | 666.67 | false | false | -# -- 2nd disb - on Feb, 15, 2025 --# - When Admin sets the business date to "15 February 2025" - When Admin successfully disburse the loan on "15 February 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 8.75 | 0.0 | 0.0 | 342.08 | 339.16 | 0.0 | 0.0 | 2.92 | - | | | 15 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 28 | 01 March 2025 | | 666.67 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 666.67 | 8.75 | 0.0 | 0.0 | 675.42 | 0.0 | 0.0 | 0.0 | 675.42 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 339.16 | 0.0 | 0.0 | 1187.09 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2025 | Repayment | 339.16 | 330.41 | 8.75 | 0.0 | 0.0 | 669.59 | false | true | - | 15 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1169.59 | false | false | - - When Loan Pay-off is made on "15 February 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4121 - Scenario: Verify cumulative multidisbursal loan with flat interest type and same_as_repeyment interest calculation period with down payment - UC4 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | - | LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_MULTIDISB_AUTO_DOWNPAYMENT | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 February 2025 | | 500.0 | 250.0 | 5.75 | 0.0 | 0.0 | 255.75 | 0.0 | 0.0 | 0.0 | 255.75 | - | 3 | 28 | 01 March 2025 | | 250.0 | 250.0 | 5.75 | 0.0 | 0.0 | 255.75 | 0.0 | 0.0 | 0.0 | 255.75 | - | 4 | 31 | 01 April 2025 | | 0.0 | 250.0 | 5.76 | 0.0 | 0.0 | 255.76 | 0.0 | 0.0 | 0.0 | 255.76 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.26 | 0.0 | 0.0 | 1017.26 | 250.0 | 0.0 | 0.0 | 767.26 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 January 2025 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | -# -- 2nd disb - on Jan, 15, 2025 --# - When Admin sets the business date to "15 January 2025" - When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 5 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 2 | 0 | 15 January 2025 | 15 January 2025 | 1125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 February 2025 | | 750.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | - | 4 | 28 | 01 March 2025 | | 375.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | - | 5 | 31 | 01 April 2025 | | 0.0 | 375.0 | 8.63 | 0.0 | 0.0 | 383.63 | 0.0 | 0.0 | 0.0 | 383.63 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 375.0 | 0.0 | 0.0 | 1150.89 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 January 2025 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | - | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1250.0 | false | false | - | 15 January 2025 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 1125.0 | false | false | - - When Loan Pay-off is made on "15 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4122 - Scenario: Verify cumulative multidisbursal loan with flat interest type and same_as_repeyment interest calculation period with approved over applied amount - UC5 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | - | LP1_INTEREST_FLAT_SAR_RECALCULATION_DAILY_360_30_APPROVED_OVER_APPLIED_MULTIDISB | 01 January 2025 | 1000 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1200" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | -# -- 2nd disb - on Feb, 15, 2025 --# - When Admin sets the business date to "15 January 2025" - When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 |1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 01 January 2025 | 15 January 2025 | 500.0 | | - - When Loan Pay-off is made on "15 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4123 - Scenario: Verify cumulative multidisbursal loan with undo last disb with flat interest type and daily interest calculation period - UC6 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | - | LP1_INTEREST_FLAT_DAILY_RECALCULATION_DAILY_360_30_MULTIDISB | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | -# -- 2nd disb - on Feb, 15, 2025 --# - When Admin sets the business date to "15 January 2025" - When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.75 | 0.0 | 0.0 | 508.75 | 0.0 | 0.0 | 0.0 | 508.75 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 26.25 | 0.0 | 0.0 | 1526.25 | 0.0 | 0.0 | 0.0 | 1526.25 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 01 January 2025 | 15 January 2025 | 500.0 | | -# --- undo last disbursement --- # - When Admin successfully undo last disbursal - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 666.67 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 2 | 28 | 01 March 2025 | | 333.34 | 333.33 | 5.83 | 0.0 | 0.0 | 339.16 | 0.0 | 0.0 | 0.0 | 339.16 | - | 3 | 31 | 01 April 2025 | | 0.0 | 333.34 | 5.84 | 0.0 | 0.0 | 339.18 | 0.0 | 0.0 | 0.0 | 339.18 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 17.5 | 0.0 | 0.0 | 1017.5 | 0.0 | 0.0 | 0.0 | 1017.5 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - - When Loan Pay-off is made on "15 January 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - - @TestRailId:C4201 - Scenario: Verify repayment reversal after adding NSF fee charge with transaction reprocessing - When Admin sets the business date to "06 November 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY | 21 August 2025 | 102.47 | 11.3 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "21 August 2025" with "102.47" amount and expected disbursement date on "21 August 2025" - And Admin successfully disburse the loan on "21 August 2025" with "102.47" EUR transaction amount - And Customer makes "AUTOPAY" repayment on "21 September 2025" with 34.80 EUR transaction amount - When Customer undo "1"th "Repayment" transaction made on "21 September 2025" - And Customer makes "AUTOPAY" repayment on "26 September 2025" with 34.79 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "29 September 2025" with 0.01 EUR transaction amount - And Customer makes "AUTOPAY" repayment on "30 September 2025" with 71.63 EUR transaction amount - When Admin adds "LOAN_NSF_FEE" due date charge with "30 September 2025" due date and 2.8 EUR transaction amount - When Customer undo "1"th "Repayment" transaction made on "26 September 2025" - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 21 August 2025 | | 102.47 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 21 September 2025 | 30 September 2025 | 68.63 | 33.84 | 0.96 | 0.0 | 0.0 | 34.8 | 34.8 | 0.0 | 34.8 | 0.0 | - | 2 | 30 | 21 October 2025 | | 34.35 | 34.28 | 0.52 | 0.0 | 2.8 | 37.6 | 36.84 | 36.84 | 0.0 | 0.76 | - | 3 | 31 | 21 November 2025 | | 0.0 | 34.35 | 0.32 | 0.0 | 0.0 | 34.67 | 0.0 | 0.0 | 0.0 | 34.67 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 102.47 | 1.8 | 0.0 | 2.8 | 107.07 | 71.64 | 36.84 | 34.8 | 35.43 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 21 August 2025 | Disbursement | 102.47 | 0.0 | 0.0 | 0.0 | 0.0 | 102.47 | false | false | - | 21 September 2025 | Repayment | 34.8 | 33.84 | 0.96 | 0.0 | 0.0 | 68.63 | true | false | - | 21 September 2025 | Accrual Activity | 0.96 | 0.0 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | - | 26 September 2025 | Repayment | 34.79 | 33.84 | 0.95 | 0.0 | 0.0 | 68.63 | true | false | - | 29 September 2025 | Repayment | 0.01 | 0.01 | 0.0 | 0.0 | 0.0 | 102.46 | false | true | - | 30 September 2025 | Repayment | 71.63 | 67.87 | 0.96 | 0.0 | 2.8 | 34.59 | false | true | - | 21 October 2025 | Accrual Activity | 3.32 | 0.0 | 0.52 | 0.0 | 2.8 | 0.0 | false | true | - | 06 November 2025 | Accrual | 1.21 | 0.0 | 1.21 | 0.0 | 0.0 | 0.0 | false | false | - And Customer makes "AUTOPAY" repayment on "06 November 2025" with 35.28 EUR transaction amount - Then Loan status will be "CLOSED_OBLIGATIONS_MET" - - @TestRailId:C4124 - Scenario: Verify cumulative multidisbursal loan that expects tranches with flat interest type and daily interest calculation period - UC7 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | - | LP1_INTEREST_FLAT_DAILY_RECALCULATION_SAR_MULTIDISB_EXPECT_TRANCHES | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | - Then Loan Transactions tab has none transaction - When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 15 January 2025 | | 500.0 | | -# -- 2nd disb - on Jan, 15, 2025 --# - When Admin sets the business date to "15 January 2025" - When Admin successfully disburse the loan on "15 January 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 15 January 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 15 January 2025 | 15 January 2025 | 500.0 | | - When Loan Pay-off is made on "15 January 2025" - Then Loan's all installments have obligations met - - @TestRailId:C4227 - Scenario: Verify cumulative multidisbursal loan that expects tranches with flat interest type and no interest calculation period - UC7.1 - When Admin sets the business date to "01 January 2025" - When Admin creates a client with random data - When Admin creates a fully customized loan with disbursements details and following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | 1st_tranche_disb_expected_date |1st_tranche_disb_principal | 2nd_tranche_disb_expected_date |2nd_tranche_disb_principal | - | LP1_INTEREST_FLAT_DAILY_ACTUAL_ACTUAL_MULTIDISB_EXPECT_TRANCHES | 01 January 2025 | 1500 | 7 | FLAT | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | 01 January 2025 | 1000.0 | 15 January 2025 | 500.0 | - And Admin successfully approves the loan on "01 January 2025" with "1500" amount and expected disbursement date on "01 January 2025" - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | - Then Loan Transactions tab has none transaction - When Admin disburses the loan on "01 January 2025" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 15 January 2025 | | 500.0 | | - When Admin sets the business date to "16 January 2025" - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | - When Admin sets the business date to "01 February 2025" - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 15 January 2025 | | 500.0 | | | 0.0 | | 0.0 | | | | 0.0 | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 5.75 | 0.0 | 0.0 | 505.75 | 0.0 | 0.0 | 0.0 | 505.75 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 5.76 | 0.0 | 0.0 | 505.76 | 0.0 | 0.0 | 0.0 | 505.76 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 17.26 | 0.0 | 0.0 | 1517.26 | 0.0 | 0.0 | 0.0 | 1517.26 | -# -- 2nd disbursement - on Feb, 1, 2025 --# - When Admin disburses the loan on "01 February 2025" with "500" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 2 | 28 | 01 March 2025 | | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - | 3 | 31 | 01 April 2025 | | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 0.0 | 0.0 | 0.0 | 508.63 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 0.0 | 0.0 | 0.0 | 1525.89 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | - Then Loan Tranche Details tab has the following data: - | Expected Disbursement On | Disbursed On | Principal | Net Disbursal Amount | - | 01 January 2025 | 01 January 2025 | 1000.0 | | - | 15 January 2025 | 01 February 2025 | 500.0 | | - Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" - When Loan Pay-off is made on "01 February 2025" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 01 February 2025 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2025 | 01 February 2025 | 1000.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 0.0 | 0.0 | 0.0 | - | 2 | 28 | 01 March 2025 | 01 February 2025 | 500.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 508.63 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2025 | 01 February 2025 | 0.0 | 500.0 | 8.63 | 0.0 | 0.0 | 508.63 | 508.63 | 508.63 | 0.0 | 0.0 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1500.0 | 25.89 | 0.0 | 0.0 | 1525.89 | 1525.89 | 1017.26 | 0.0 | 0.0 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2025 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | false | false | - | 01 February 2025 | Repayment | 1525.89 | 1500.0 | 25.89 | 0.0 | 0.0 | 0.0 | false | false | - | 01 February 2025 | Accrual | 25.89 | 0.0 | 25.89 | 0.0 | 0.0 | 0.0 | false | false | - - @TestRailId:C4643 - Scenario: Verify that changedTerms is false in LoanDisbursalTransactionBusinessEvent for initial disbursement - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 1000 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024" - When Admin disburses the loan on "01 January 2024" with "1000" EUR transaction amount - Then Loan Repayment schedule has 3 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 15 | 16 January 2024 | | 667.64 | 332.36 | 2.92 | 0.0 | 0.0 | 335.28 | 0.0 | 0.0 | 0.0 | 335.28 | - | 2 | 15 | 31 January 2024 | | 334.31 | 333.33 | 1.95 | 0.0 | 0.0 | 335.28 | 0.0 | 0.0 | 0.0 | 335.28 | - | 3 | 15 | 15 February 2024 | | 0.0 | 334.31 | 0.98 | 0.0 | 0.0 | 335.29 | 0.0 | 0.0 | 0.0 | 335.29 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 1000.0 | 5.85 | 0.0 | 0.0 | 1005.85 | 0.0 | 0.0 | 0.0 | 1005.85 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" - - @TestRailId:C4645 - Scenario: Verify that changedTerms is false in LoanDisbursalTransactionBusinessEvent when additional disbursement does not change terms - When Admin sets the business date to "01 January 2024" - When Admin creates a client with random data - When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule - When Admin creates a fully customized loan with the following data: - | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | - | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE | 01 January 2024 | 300 | 9.4822 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | - And Admin successfully approves the loan on "01 January 2024" with "300" amount and expected disbursement date on "01 January 2024" - When Admin disburses the loan on "01 January 2024" with "100" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 83.66 | 16.34 | 0.79 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - | 2 | 29 | 01 March 2024 | | 67.19 | 16.47 | 0.66 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - | 3 | 31 | 01 April 2024 | | 50.59 | 16.6 | 0.53 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - | 4 | 30 | 01 May 2024 | | 33.86 | 16.73 | 0.4 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - | 5 | 31 | 01 June 2024 | | 17.0 | 16.86 | 0.27 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - | 6 | 30 | 01 July 2024 | | 0.0 | 17.0 | 0.13 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.78 | 0.0 | 0.0 | 102.78 | 0.0 | 0.0 | 0.0 | 102.78 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | - Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" - When Admin sets the business date to "08 January 2024" - When Admin disburses the loan on "08 January 2024" with "200" EUR transaction amount - Then Loan Repayment schedule has 6 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | | | 08 January 2024 | | 200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 250.68 | 49.32 | 2.01 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - | 2 | 29 | 01 March 2024 | | 201.33 | 49.35 | 1.98 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - | 3 | 31 | 01 April 2024 | | 151.59 | 49.74 | 1.59 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - | 4 | 30 | 01 May 2024 | | 101.46 | 50.13 | 1.2 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - | 5 | 31 | 01 June 2024 | | 50.93 | 50.53 | 0.8 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - | 6 | 30 | 01 July 2024 | | 0.0 | 50.93 | 0.4 | 0.0 | 0.0 | 51.33 | 0.0 | 0.0 | 0.0 | 51.33 | - Then Loan Repayment schedule has the following data in Total row: - | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 300.0 | 7.98 | 0.0 | 0.0 | 307.98 | 0.0 | 0.0 | 0.0 | 307.98 | - Then Loan Transactions tab has the following data: - | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | - | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | - | 08 January 2024 | Disbursement | 200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 300.0 | false | false | - Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" - When Loan Pay-off is made on "08 January 2024" - Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature index 1c5b3060729..b144cfeaeb3 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -8054,3 +8054,63 @@ Feature: LoanCharge Then LoanDisbursalTransactionBusinessEvent has changedTerms "false" When Loan Pay-off is made on "01 March 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4689 + Scenario: Verify that loan modification recalculates percentage charge based on new interest, not accumulate old and new interest + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INSTALLMENT_FEE_PERCENT_AMOUNT_CHARGES | 01 January 2026 | 100 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | +# --- 10% Processing fee added --- + And Admin adds a 10 % Processing charge to the loan with "en" locale on date: "06 January 2026" - no event + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2026 | | 83.85 | 16.15 | 0.85 | 10.29 | 0.0 | 27.29 | 0.0 | 0.0 | 0.0 | 27.29 | + | 2 | 28 | 01 March 2026 | | 67.49 | 16.36 | 0.64 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 3 | 31 | 01 April 2026 | | 51.06 | 16.43 | 0.57 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 4 | 30 | 01 May 2026 | | 34.48 | 16.58 | 0.42 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 5 | 31 | 01 June 2026 | | 17.77 | 16.71 | 0.29 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 6 | 30 | 01 July 2026 | | 0.0 | 17.77 | 0.15 | 0.0 | 0.0 | 17.92 | 0.0 | 0.0 | 0.0 | 17.92 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.92 | 10.29 | 0.0 | 113.21 | 0.0 | 0.0 | 0.0 | 113.21 | + And Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | % Processing fee | false | Specified due date | 06 January 2026 | % Loan Amount + Interest | 10.29 | 0.0 | 0.0 | 10.29 | +# --- Approve and undo Approval --- + When Admin successfully approves the loan on "01 January 2026" with "100" amount and expected disbursement date on "01 January 2026" + And Admin can successfully undone the loan approval + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2026 | | 83.85 | 16.15 | 0.85 | 10.29 | 0.0 | 27.29 | 0.0 | 0.0 | 0.0 | 27.29 | + | 2 | 28 | 01 March 2026 | | 67.49 | 16.36 | 0.64 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 3 | 31 | 01 April 2026 | | 51.06 | 16.43 | 0.57 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 4 | 30 | 01 May 2026 | | 34.48 | 16.58 | 0.42 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 5 | 31 | 01 June 2026 | | 17.77 | 16.71 | 0.29 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 6 | 30 | 01 July 2026 | | 0.0 | 17.77 | 0.15 | 0.0 | 0.0 | 17.92 | 0.0 | 0.0 | 0.0 | 17.92 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.92 | 10.29 | 0.0 | 113.21 | 0.0 | 0.0 | 0.0 | 113.21 | + And Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | % Processing fee | false | Specified due date | 06 January 2026 | % Loan Amount + Interest | 10.29 | 0.0 | 0.0 | 10.29 | +# --- Modify interest rate --- + When Admin modifies the loan and changes the ANNUAL interest rate to "9" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2026 | | 83.76 | 16.24 | 0.76 | 10.26 | 0.0 | 27.26 | 0.0 | 0.0 | 0.0 | 27.26 | + | 2 | 28 | 01 March 2026 | | 67.34 | 16.42 | 0.58 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 3 | 31 | 01 April 2026 | | 50.85 | 16.49 | 0.51 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 4 | 30 | 01 May 2026 | | 34.23 | 16.62 | 0.38 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 5 | 31 | 01 June 2026 | | 17.49 | 16.74 | 0.26 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 6 | 30 | 01 July 2026 | | 0.0 | 17.49 | 0.13 | 0.0 | 0.0 | 17.62 | 0.0 | 0.0 | 0.0 | 17.62 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.62 | 10.26 | 0.0 | 112.88 | 0.0 | 0.0 | 0.0 | 112.88 | + And Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | % Processing fee | false | Specified due date | 06 January 2026 | % Loan Amount + Interest | 10.26 | 0.0 | 0.0 | 10.26 | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature index c06637716eb..a820c5b5a3f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature @@ -1496,3 +1496,74 @@ Feature: Contract Termination When Loan Pay-off is made on "01 March 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4694 @AdvancedPaymentAllocation + Scenario: Verify contract termination after repeated re-aging across month-end should not fail due to accumulated stub periods + When Admin sets the business date to "28 January 2026" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 28 January 2026 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "28 January 2026" with "100" amount and expected disbursement date on "28 January 2026" + When Admin successfully disburse the loan on "28 January 2026" with "100" EUR transaction amount +# --- Re-age 4 times across late-January dates --- + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "29 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "30 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "31 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | +# --- Verify schedule after 4 re-ages: 7 periods (1 collapsed stub + 6 re-aged) --- + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 3 | 31 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 28 | 28 February 2026 | | 83.62 | 16.38 | 0.64 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 3 | 28 | 28 March 2026 | | 67.09 | 16.53 | 0.49 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 4 | 31 | 28 April 2026 | | 50.46 | 16.63 | 0.39 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 5 | 30 | 28 May 2026 | | 33.73 | 16.73 | 0.29 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 6 | 31 | 28 June 2026 | | 16.91 | 16.82 | 0.2 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.91 | 0.1 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.11 | 0.0 | 0.0 | 102.11 | 0.0 | 0.0 | 0.0 | 102.11 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2026 | Re-age | 100.04 | 100.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 31 January 2026 | Re-age | 100.06 | 100.0 | 0.06 | 0.0 | 0.0 | 0.0 | false | false | +# --- Contract termination on 01 February 2026: should not fail due to accumulated stubs --- + When Admin sets the business date to "01 February 2026" + And Admin successfully terminates loan contract +# --- Expected: 2 periods (1 collapsed stub + 1 termination period on 01 February 2026) --- +# --- The termination period should contain: full remaining principal (100.0) + accrued interest from Jan 28 to Feb 1 --- + Then Loan Repayment schedule has 2 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 3 | 31 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 1 | 01 February 2026 | | 0.0 | 100.0 | 0.08 | 0.0 | 0.0 | 100.08 | 0.0 | 0.0 | 0.0 | 100.08 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 0.08 | 0.0 | 0.0 | 100.08 | 0.0 | 0.0 | 0.0 | 100.08 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2026 | Re-age | 100.04 | 100.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 31 January 2026 | Re-age | 100.06 | 100.0 | 0.06 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2026 | Accrual | 0.08 | 0.0 | 0.08 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2026 | Contract Termination | 100.08 | 100.0 | 0.08 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "01 February 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestRateChange.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestRateChange.feature index 4baf17b8bba..b5aad8f8063 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestRateChange.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestRateChange.feature @@ -1701,3 +1701,556 @@ Feature: Loan interest rate change on repayment schedule Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1000.0 | 3.68 | 0.0 | 0.0 | 1003.68 | 0.0 | 0.0 | 0.0 | 1003.68 | + + @TestRailId:C4625 + Scenario: Verify loan closure after MIR, backdated interest rate change and repayment reversal + When Admin sets the business date to "03 October 2025" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 03 October 2025 | 231.59 | 35.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "03 October 2025" with "231.59" amount and expected disbursement date on "03 October 2025" + And Admin successfully disburse the loan on "03 October 2025" with "231.59" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.79 | 35.8 | 6.95 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 2 | 30 | 03 December 2025 | | 158.91 | 36.88 | 5.87 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 120.93 | 37.98 | 4.77 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 81.81 | 39.12 | 3.63 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 5 | 28 | 03 March 2026 | | 41.51 | 40.3 | 2.45 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.51 | 1.24 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 24.91 | 0.0 | 0.0 | 256.5 | 0.0 | 0.0 | 0.0 | 256.5 | + When Admin sets the business date to "15 October 2025" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 October 2025" with 220.83 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 213.75 | 17.84 | 2.89 | 0.0 | 0.0 | 20.73 | 9.65 | 9.65 | 0.0 | 11.08 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.89 | 0.0 | 0.0 | 234.48 | 223.4 | 223.4 | 0.0 | 11.08 | + When Admin sets the business date to "30 October 2025" + And Customer makes "AUTOPAY" repayment on "30 October 2025" with 11.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 213.75 | 17.84 | 2.84 | 0.0 | 0.0 | 20.68 | 20.68 | 20.68 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.84 | 0.0 | 0.0 | 234.43 | 234.43 | 234.43 | 0.0 | 0.0 | + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 04 October 2025 | 30 October 2025 | | | | | 25.99 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + And Admin sets the business date to "06 November 2025" + And Admin makes Credit Balance Refund transaction on "06 November 2025" with 0.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + When Admin sets the business date to "07 November 2025" + And Customer undo "1"th "Repayment" transaction made on "30 October 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 14.78 | 14.78 | 0.0 | 11.01 | + | 2 | 30 | 03 December 2025 | | 166.32 | 41.62 | 0.03 | 0.0 | 0.0 | 41.65 | 41.58 | 41.58 | 0.0 | 0.07 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.13 | 0.0 | 0.0 | 233.76 | 222.68 | 222.68 | 0.0 | 11.08 | + When Admin sets the business date to "10 November 2025" + And Customer makes "AUTOPAY" repayment on "10 November 2025" with 11.05 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 10 November 2025 | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 25.79 | 14.78 | 11.01 | 0.0 | + | 2 | 30 | 03 December 2025 | 10 November 2025 | 166.32 | 41.62 | 0.0 | 0.0 | 0.0 | 41.62 | 41.62 | 41.62 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.1 | 0.0 | 0.0 | 233.73 | 233.73 | 222.72 | 11.01 | 0.0 | + And Loan is closed with zero outstanding balance and it's all installments have obligations met + + + @TestRailId:C4626 + Scenario: Verify loan closure after MIR, backdated interest rate change and repayment reversal, allocation rule: LAST INSTALLMENT + When Admin sets the business date to "03 October 2025" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 03 October 2025 | 231.59 | 35.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "03 October 2025" with "231.59" amount and expected disbursement date on "03 October 2025" + And Admin successfully disburse the loan on "03 October 2025" with "231.59" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.79 | 35.8 | 6.95 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 2 | 30 | 03 December 2025 | | 158.91 | 36.88 | 5.87 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 120.93 | 37.98 | 4.77 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 81.81 | 39.12 | 3.63 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 5 | 28 | 03 March 2026 | | 41.51 | 40.3 | 2.45 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.51 | 1.24 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 24.91 | 0.0 | 0.0 | 256.5 | 0.0 | 0.0 | 0.0 | 256.5 | + When Admin sets the business date to "15 October 2025" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 October 2025" with 220.83 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 213.75 | 17.84 | 2.89 | 0.0 | 0.0 | 20.73 | 9.65 | 9.65 | 0.0 | 11.08 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.89 | 0.0 | 0.0 | 234.48 | 223.4 | 223.4 | 0.0 | 11.08 | + When Admin sets the business date to "30 October 2025" + And Customer makes "AUTOPAY" repayment on "30 October 2025" with 11.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 213.75 | 17.84 | 2.84 | 0.0 | 0.0 | 20.68 | 20.68 | 20.68 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.84 | 0.0 | 0.0 | 234.43 | 234.43 | 234.43 | 0.0 | 0.0 | + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 04 October 2025 | 30 October 2025 | | | | | 25.99 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + And Admin sets the business date to "06 November 2025" + And Admin makes Credit Balance Refund transaction on "06 November 2025" with 0.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + When Admin sets the business date to "07 November 2025" + And Customer undo "1"th "Repayment" transaction made on "30 October 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 14.78 | 14.78 | 0.0 | 11.01 | + | 2 | 30 | 03 December 2025 | | 166.32 | 41.62 | 0.03 | 0.0 | 0.0 | 41.65 | 41.58 | 41.58 | 0.0 | 0.07 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.13 | 0.0 | 0.0 | 233.76 | 222.68 | 222.68 | 0.0 | 11.08 | + When Admin sets the business date to "10 November 2025" + And Customer makes "AUTOPAY" repayment on "10 November 2025" with 11.05 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 10 November 2025 | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 25.79 | 14.78 | 11.01 | 0.0 | + | 2 | 30 | 03 December 2025 | 10 November 2025 | 166.32 | 41.62 | 0.0 | 0.0 | 0.0 | 41.62 | 41.62 | 41.62 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.1 | 0.0 | 0.0 | 233.73 | 233.73 | 222.72 | 11.01 | 0.0 | + And Loan is closed with zero outstanding balance and it's all installments have obligations met + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C4679 + Scenario: Verify loan closure after MIR, backdated interest rate change, repayment reversal and COB recalculation + When Admin sets the business date to "03 October 2025" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 03 October 2025 | 231.59 | 35.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "03 October 2025" with "231.59" amount and expected disbursement date on "03 October 2025" + And Admin successfully disburse the loan on "03 October 2025" with "231.59" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.79 | 35.8 | 6.95 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 2 | 30 | 03 December 2025 | | 158.91 | 36.88 | 5.87 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 120.93 | 37.98 | 4.77 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 81.81 | 39.12 | 3.63 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 5 | 28 | 03 March 2026 | | 41.51 | 40.3 | 2.45 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.51 | 1.24 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 24.91 | 0.0 | 0.0 | 256.5 | 0.0 | 0.0 | 0.0 | 256.5 | + When Admin sets the business date to "15 October 2025" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 October 2025" with 220.83 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 213.75 | 17.84 | 2.89 | 0.0 | 0.0 | 20.73 | 9.65 | 9.65 | 0.0 | 11.08 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.89 | 0.0 | 0.0 | 234.48 | 223.4 | 223.4 | 0.0 | 11.08 | + When Admin sets the business date to "30 October 2025" + And Customer makes "AUTOPAY" repayment on "30 October 2025" with 11.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 213.75 | 17.84 | 2.84 | 0.0 | 0.0 | 20.68 | 20.68 | 20.68 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.84 | 0.0 | 0.0 | 234.43 | 234.43 | 234.43 | 0.0 | 0.0 | + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 04 October 2025 | 30 October 2025 | | | | | 25.99 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + And Admin sets the business date to "06 November 2025" + And Admin makes Credit Balance Refund transaction on "06 November 2025" with 0.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + When Admin sets the business date to "07 November 2025" + And Customer undo "1"th "Repayment" transaction made on "30 October 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 14.78 | 14.78 | 0.0 | 11.01 | + | 2 | 30 | 03 December 2025 | | 166.32 | 41.62 | 0.03 | 0.0 | 0.0 | 41.65 | 41.58 | 41.58 | 0.0 | 0.07 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.13 | 0.0 | 0.0 | 233.76 | 222.68 | 222.68 | 0.0 | 11.08 | + When Admin sets the business date to "08 November 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 14.78 | 14.78 | 0.0 | 11.01 | + | 2 | 30 | 03 December 2025 | | 166.32 | 41.62 | 0.04 | 0.0 | 0.0 | 41.66 | 41.58 | 41.58 | 0.0 | 0.08 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.14 | 0.0 | 0.0 | 233.77 | 222.68 | 222.68 | 0.0 | 11.09 | + When Admin sets the business date to "10 November 2025" + And Customer makes "AUTOPAY" repayment on "10 November 2025" with 11.05 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 10 November 2025 | 207.9 | 23.69 | 2.1 | 0.0 | 0.0 | 25.79 | 25.79 | 14.78 | 11.01 | 0.0 | + | 2 | 30 | 03 December 2025 | 10 November 2025 | 166.32 | 41.62 | 0.0 | 0.0 | 0.0 | 41.62 | 41.62 | 41.62 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 2.1 | 0.0 | 0.0 | 233.73 | 233.73 | 222.72 | 11.01 | 0.0 | + And Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4680 + Scenario: Verify overdueBalanceCorrectionAmount reset after MIR reversal with backdated interest rate change + When Admin sets the business date to "03 October 2025" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 03 October 2025 | 231.59 | 35.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "03 October 2025" with "231.59" amount and expected disbursement date on "03 October 2025" + And Admin successfully disburse the loan on "03 October 2025" with "231.59" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.79 | 35.8 | 6.95 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 2 | 30 | 03 December 2025 | | 158.91 | 36.88 | 5.87 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 120.93 | 37.98 | 4.77 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 81.81 | 39.12 | 3.63 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 5 | 28 | 03 March 2026 | | 41.51 | 40.3 | 2.45 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.51 | 1.24 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 24.91 | 0.0 | 0.0 | 256.5 | 0.0 | 0.0 | 0.0 | 256.5 | + When Admin sets the business date to "15 October 2025" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 October 2025" with 220.83 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 213.75 | 17.84 | 2.89 | 0.0 | 0.0 | 20.73 | 9.65 | 9.65 | 0.0 | 11.08 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.89 | 0.0 | 0.0 | 234.48 | 223.4 | 223.4 | 0.0 | 11.08 | + When Admin sets the business date to "30 October 2025" + And Customer makes "AUTOPAY" repayment on "30 October 2025" with 11.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 213.75 | 17.84 | 2.84 | 0.0 | 0.0 | 20.68 | 20.68 | 20.68 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 171.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 128.25 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 85.5 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.84 | 0.0 | 0.0 | 234.43 | 234.43 | 234.43 | 0.0 | 0.0 | + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 04 October 2025 | 30 October 2025 | | | | | 25.99 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + And Admin sets the business date to "06 November 2025" + And Admin makes Credit Balance Refund transaction on "06 November 2025" with 0.04 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | 30 October 2025 | 207.9 | 23.69 | 2.05 | 0.0 | 0.0 | 25.74 | 25.74 | 25.74 | 0.0 | 0.0 | + | 2 | 30 | 03 December 2025 | 15 October 2025 | 166.32 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 3 | 31 | 03 January 2026 | 15 October 2025 | 124.74 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 4 | 31 | 03 February 2026 | 15 October 2025 | 83.16 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 2.05 | 0.0 | 0.0 | 233.64 | 233.64 | 233.64 | 0.0 | 0.0 | + When Admin sets the business date to "07 November 2025" + And Customer undo "1"th "Merchant Issued Refund" transaction made on "15 October 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.01 | 36.58 | 5.0 | 0.0 | 0.0 | 41.58 | 11.04 | 11.04 | 0.0 | 30.54 | + | 2 | 30 | 03 December 2025 | | 157.74 | 37.31 | 4.31 | 0.0 | 0.0 | 41.62 | 0.0 | 0.0 | 0.0 | 41.62 | + | 3 | 31 | 03 January 2026 | | 119.58 | 38.16 | 3.42 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 80.59 | 38.99 | 2.59 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 5 | 28 | 03 March 2026 | | 40.76 | 39.83 | 1.75 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 6 | 31 | 03 April 2026 | | 0.0 | 40.76 | 0.88 | 0.0 | 0.0 | 41.64 | 0.0 | 0.0 | 0.0 | 41.64 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 17.95 | 0.0 | 0.0 | 249.58 | 11.04 | 11.04 | 0.0 | 238.54 | + # COB on Nov 8: tests overdueBalanceCorrectionAmount after massive reset + COB recalculation + When Admin sets the business date to "08 November 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.01 | 36.58 | 5.0 | 0.0 | 0.0 | 41.58 | 11.04 | 11.04 | 0.0 | 30.54 | + | 2 | 30 | 03 December 2025 | | 157.76 | 37.29 | 4.33 | 0.0 | 0.0 | 41.62 | 0.0 | 0.0 | 0.0 | 41.62 | + | 3 | 31 | 03 January 2026 | | 119.6 | 38.16 | 3.42 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 80.61 | 38.99 | 2.59 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 5 | 28 | 03 March 2026 | | 40.78 | 39.83 | 1.75 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 6 | 31 | 03 April 2026 | | 0.0 | 40.78 | 0.88 | 0.0 | 0.0 | 41.66 | 0.0 | 0.0 | 0.0 | 41.66 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.63 | 17.97 | 0.0 | 0.0 | 249.6 | 11.04 | 11.04 | 0.0 | 238.56 | + When Loan Pay-off is made on "08 November 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4681 + Scenario: Verify overdueBalanceCorrectionAmount with partial MIR and multiple overdue periods + When Admin sets the business date to "03 October 2025" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 03 October 2025 | 231.59 | 35.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "03 October 2025" with "231.59" amount and expected disbursement date on "03 October 2025" + And Admin successfully disburse the loan on "03 October 2025" with "231.59" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.79 | 35.8 | 6.95 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 2 | 30 | 03 December 2025 | | 158.91 | 36.88 | 5.87 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 120.93 | 37.98 | 4.77 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 81.81 | 39.12 | 3.63 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 5 | 28 | 03 March 2026 | | 41.51 | 40.3 | 2.45 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.51 | 1.24 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 24.91 | 0.0 | 0.0 | 256.5 | 0.0 | 0.0 | 0.0 | 256.5 | + When Admin sets the business date to "15 October 2025" + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "15 October 2025" with 100 EUR transaction amount and system-generated Idempotency key + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 193.95 | 37.64 | 5.11 | 0.0 | 0.0 | 42.75 | 1.16 | 1.16 | 0.0 | 41.59 | + | 2 | 30 | 03 December 2025 | | 154.02 | 39.93 | 2.82 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 3 | 31 | 03 January 2026 | | 112.89 | 41.13 | 1.62 | 0.0 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | + | 4 | 31 | 03 February 2026 | | 85.5 | 27.39 | 0.39 | 0.0 | 0.0 | 27.78 | 14.5 | 14.5 | 0.0 | 13.28 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 42.75 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 42.75 | 0.0 | 0.0 | 0.0 | 42.75 | 42.75 | 42.75 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 9.94 | 0.0 | 0.0 | 241.53 | 101.16 | 101.16 | 0.0 | 140.37 | + When Admin sets the business date to "30 October 2025" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 04 October 2025 | 30 October 2025 | | | | | 25.99 | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 193.7 | 37.89 | 3.69 | 0.0 | 0.0 | 41.58 | 0.84 | 0.84 | 0.0 | 40.74 | + | 2 | 30 | 03 December 2025 | | 154.15 | 39.55 | 2.03 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 3 | 31 | 03 January 2026 | | 113.74 | 40.41 | 1.17 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 83.16 | 30.58 | 0.3 | 0.0 | 0.0 | 30.88 | 16.84 | 16.84 | 0.0 | 14.04 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 7.19 | 0.0 | 0.0 | 238.78 | 100.84 | 100.84 | 0.0 | 137.94 | + # COB on Dec 4: triggers recalculateModelOverdueAmountsTillDate with 2 overdue periods + When Admin sets the business date to "04 December 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 193.7 | 37.89 | 3.69 | 0.0 | 0.0 | 41.58 | 0.84 | 0.84 | 0.0 | 40.74 | + | 2 | 30 | 03 December 2025 | | 154.97 | 38.73 | 2.85 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 3 | 31 | 03 January 2026 | | 114.63 | 40.34 | 1.24 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 83.16 | 31.47 | 0.32 | 0.0 | 0.0 | 31.79 | 16.84 | 16.84 | 0.0 | 14.95 | + | 5 | 28 | 03 March 2026 | 15 October 2025 | 41.58 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + | 6 | 31 | 03 April 2026 | 15 October 2025 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | 41.58 | 41.58 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 8.1 | 0.0 | 0.0 | 239.69 | 100.84 | 100.84 | 0.0 | 138.85 | + When Admin sets the business date to "05 December 2025" + And Customer undo "1"th "Merchant Issued Refund" transaction made on "15 October 2025" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.03 | 36.56 | 5.02 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 2 | 30 | 03 December 2025 | | 158.47 | 36.56 | 5.02 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 3 | 31 | 03 January 2026 | | 120.42 | 38.05 | 3.53 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 81.45 | 38.97 | 2.61 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 5 | 28 | 03 March 2026 | | 41.63 | 39.82 | 1.76 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.63 | 0.9 | 0.0 | 0.0 | 42.53 | 0.0 | 0.0 | 0.0 | 42.53 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 18.84 | 0.0 | 0.0 | 250.43 | 0.0 | 0.0 | 0.0 | 250.43 | + # COB after reversal: tests that overdueBalanceCorrectionAmount is properly rebuilt for all overdue periods + When Admin sets the business date to "06 December 2025" + And Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 03 October 2025 | | 231.59 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 03 November 2025 | | 195.03 | 36.56 | 5.02 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 2 | 30 | 03 December 2025 | | 158.47 | 36.56 | 5.02 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 3 | 31 | 03 January 2026 | | 120.48 | 37.99 | 3.59 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 4 | 31 | 03 February 2026 | | 81.51 | 38.97 | 2.61 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 5 | 28 | 03 March 2026 | | 41.7 | 39.81 | 1.77 | 0.0 | 0.0 | 41.58 | 0.0 | 0.0 | 0.0 | 41.58 | + | 6 | 31 | 03 April 2026 | | 0.0 | 41.7 | 0.9 | 0.0 | 0.0 | 42.6 | 0.0 | 0.0 | 0.0 | 42.6 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 231.59 | 18.91 | 0.0 | 0.0 | 250.5 | 0.0 | 0.0 | 0.0 | 250.5 | + When Loan Pay-off is made on "06 December 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature index 1b87f67077b..99b26eaff20 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature @@ -1529,7 +1529,7 @@ Feature: LoanReAging | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | | 01 March 2025 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | | 03 May 2025 | Chargeback | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 875.0 | false | - | 01 April 2025 | Re-age | 750.0 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | + | 01 April 2025 | Re-age | 750.0 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | When Loan Pay-off is made on "03 May 2025" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -11172,3 +11172,52 @@ Feature: LoanReAging Then LoanReAgeTransactionBusinessEvent has changedTerms "false" When Loan Pay-off is made on "15 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4693 @AdvancedPaymentAllocation + Scenario: Verify that repeated re-aging across month-end should not accumulate stub periods + When Admin sets the business date to "28 January 2026" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 28 January 2026 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "28 January 2026" with "100" amount and expected disbursement date on "28 January 2026" + When Admin successfully disburse the loan on "28 January 2026" with "100" EUR transaction amount +# --- Re-age 4 times across late-January dates --- + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "29 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "30 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + When Admin sets the business date to "31 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | +# --- Verify schedule: should be 7 periods (1 collapsed stub + 6 re-aged), NOT 10+ with accumulated 1-day stubs --- + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 3 | 31 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 28 | 28 February 2026 | | 83.62 | 16.38 | 0.64 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 3 | 28 | 28 March 2026 | | 67.09 | 16.53 | 0.49 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 4 | 31 | 28 April 2026 | | 50.46 | 16.63 | 0.39 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 5 | 30 | 28 May 2026 | | 33.73 | 16.73 | 0.29 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 6 | 31 | 28 June 2026 | | 16.91 | 16.82 | 0.2 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.91 | 0.1 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.11 | 0.0 | 0.0 | 102.11 | 0.0 | 0.0 | 0.0 | 102.11 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2026 | Re-age | 100.04 | 100.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 31 January 2026 | Re-age | 100.06 | 100.0 | 0.06 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "31 January 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingEqualAmortization.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingEqualAmortization.feature index 5a0f02d3b04..c8f64d290e8 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingEqualAmortization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingEqualAmortization.feature @@ -9366,16 +9366,16 @@ Feature: LoanReAgingEqualAmortization | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | | 1 | MONTHS | 15 February 2024 | 6 | EQUAL_AMORTIZATION_FULL_INTEREST | Then Loan Repayment schedule has 8 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 14 | 15 February 2024 | 15 February 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 3 | 0 | 15 February 2024 | | 69.64 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | - | 4 | 29 | 15 March 2024 | | 55.71 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | - | 5 | 31 | 15 April 2024 | | 41.77 | 13.94 | 0.24 | 0.0 | 0.0 | 14.18 | 0.0 | 0.0 | 0.0 | 14.18 | - | 6 | 30 | 15 May 2024 | | 27.83 | 13.94 | 0.24 | 0.0 | 0.0 | 14.18 | 0.0 | 0.0 | 0.0 | 14.18 | - | 7 | 31 | 15 June 2024 | | 13.89 | 13.94 | 0.24 | 0.0 | 0.0 | 14.18 | 0.0 | 0.0 | 0.0 | 14.18 | - | 8 | 30 | 15 July 2024 | | 0.0 | 13.89 | 0.27 | 0.0 | 0.0 | 14.16 | 0.0 | 0.0 | 0.0 | 14.16 | + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 14 | 15 February 2024 | 15 February 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 0 | 15 February 2024 | | 69.64 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | + | 4 | 29 | 15 March 2024 | | 55.71 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | + | 5 | 31 | 15 April 2024 | | 41.78 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | + | 6 | 30 | 15 May 2024 | | 27.85 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | + | 7 | 31 | 15 June 2024 | | 13.92 | 13.93 | 0.24 | 0.0 | 0.0 | 14.17 | 0.0 | 0.0 | 0.0 | 14.17 | + | 8 | 30 | 15 July 2024 | | 0.0 | 13.92 | 0.27 | 0.0 | 0.0 | 14.19 | 0.0 | 0.0 | 0.0 | 14.19 | And Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | @@ -11650,3 +11650,335 @@ Feature: LoanReAgingEqualAmortization | 01 April 2024 | Close (as written-off) | 85.08 | 83.57 | 1.51 | 0.0 | 0.0 | 0.0 | false | false | Then Loan has 0 outstanding amount Then Loan's all installments have obligations met + + @TestRailId:C70226 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging transaction with after maturity date with charge n+1 - interest bearing loan with equal amortization; outstanding full interest - UC1 + When Admin sets the business date to "10 March 2026" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_AUTO_DOWNPAYMENT | 01 January 2026 | 1000 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 2 | MONTHS | 1 | MONTHS | 2 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "1000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 250.0 | 0.0 | 0.0 | 750.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | +# --- repayment at current date --- # + And Customer makes "AUTOPAY" repayment on "10 March 2026" with 100 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 350.0 | 0.0 | 100.0 | 650.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + And Admin adds "LOAN_NSF_FEE" due date charge with "10 March 2026" due date and 12 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 4 | 9 | 10 March 2026 | | 0.0 | 0.0 | 0.0 | 0.0 | 12.0 | 12.0 | 0.0 | 0.0 | 0.0 | 12.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 12.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | NSF fee | true | Specified due date | 10 March 2026 | Flat | 12.0 | 0.0 | 0.0 | 12.0 | +# --- re-age transaction --- # + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 10 April 2026 | 6 | EQUAL_AMORTIZATION_FULL_INTEREST | + Then Loan Repayment schedule has 10 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | 10 March 2026 | 650.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 100.0 | 0.0 | + | 3 | 28 | 01 March 2026 | 10 March 2026 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 9 | 10 March 2026 | 10 March 2026 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 10 April 2026 | | 541.67 | 108.33 | 0.0 | 0.0 | 2.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 6 | 30 | 10 May 2026 | | 433.34 | 108.33 | 0.0 | 0.0 | 2.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 7 | 31 | 10 June 2026 | | 325.01 | 108.33 | 0.0 | 0.0 | 2.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 8 | 30 | 10 July 2026 | | 216.68 | 108.33 | 0.0 | 0.0 | 2.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 9 | 31 | 10 August 2026 | | 108.35 | 108.33 | 0.0 | 0.0 | 2.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 10 | 31 | 10 September 2026 | | 0.0 | 108.35 | 0.0 | 0.0 | 2.0 | 110.35 | 0.0 | 0.0 | 0.0 | 110.35 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 12.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + | 10 March 2026 | Re-age | 662.0 | 650.0 | 0.0 | 0.0 | 12.0 | 0.0 | false | false | + When Loan Pay-off is made on "10 March 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C70227 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging transaction with after maturity date with charge n+1 - interest bearing loan with equal amortization; outstanding payable interest - UC2 + When Admin sets the business date to "10 March 2026" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_AUTO_DOWNPAYMENT | 01 January 2026 | 1000 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 2 | MONTHS | 1 | MONTHS | 2 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "1000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 250.0 | 0.0 | 0.0 | 750.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | +# --- repayment at current date --- # + And Customer makes "AUTOPAY" repayment on "10 March 2026" with 100 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 350.0 | 0.0 | 100.0 | 650.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + And Admin adds "LOAN_SNOOZE_FEE" due date charge with "10 March 2026" due date and 12 EUR transaction amount + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | Snooze fee | false | Specified due date | 10 March 2026 | Flat | 12.0 | 0.0 | 0.0 | 12.0 | + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 4 | 9 | 10 March 2026 | | 0.0 | 0.0 | 0.0 | 12.0 | 0.0 | 12.0 | 0.0 | 0.0 | 0.0 | 12.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 12.0 | 0.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | +# --- re-age transaction --- # + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 10 April 2026 | 6 | EQUAL_AMORTIZATION_PAYABLE_INTEREST | + Then Loan Repayment schedule has 10 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | 10 March 2026 | 650.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 100.0 | 0.0 | + | 3 | 28 | 01 March 2026 | 10 March 2026 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 9 | 10 March 2026 | 10 March 2026 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 10 April 2026 | | 541.67 | 108.33 | 0.0 | 2.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 6 | 30 | 10 May 2026 | | 433.34 | 108.33 | 0.0 | 2.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 7 | 31 | 10 June 2026 | | 325.01 | 108.33 | 0.0 | 2.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 8 | 30 | 10 July 2026 | | 216.68 | 108.33 | 0.0 | 2.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 9 | 31 | 10 August 2026 | | 108.35 | 108.33 | 0.0 | 2.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 10 | 31 | 10 September 2026 | | 0.0 | 108.35 | 0.0 | 2.0 | 0.0 | 110.35 | 0.0 | 0.0 | 0.0 | 110.35 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 12.0 | 0.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + | 10 March 2026 | Re-age | 662.0 | 650.0 | 0.0 | 12.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "10 March 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C70228 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging transaction with after maturity date with chargeback n+1 - interest bearing loan with equal amortization; outstanding payable interest - UC3 + When Admin sets the business date to "10 March 2026" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_AUTO_DOWNPAYMENT | 01 January 2026 | 1000 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 2 | MONTHS | 1 | MONTHS | 2 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "1000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 250.0 | 0.0 | 0.0 | 750.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | +# --- repayment at current date --- # + And Customer makes "AUTOPAY" repayment on "10 March 2026" with 100 EUR transaction amount + Then Loan Repayment schedule has 3 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 350.0 | 0.0 | 100.0 | 650.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + # And Admin adds "LOAN_NSF_FEE" due date charge with "10 March 2026" due date and 12 EUR transaction amount + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 12 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 100.0 | 0.0 | 100.0 | 275.0 | + | 3 | 28 | 01 March 2026 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 4 | 9 | 10 March 2026 | | 0.0 | 12.0 | 0.0 | 0.0 | 0.0 | 12.0 | 0.0 | 0.0 | 0.0 | 12.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1012.0 | 0.0 | 0.0 | 0.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + | 10 March 2026 | Chargeback | 12.0 | 12.0 | 0.0 | 0.0 | 0.0 | 662.0 | false | false | +# --- re-age transaction --- # + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 10 April 2026 | 6 | EQUAL_AMORTIZATION_PAYABLE_INTEREST | + Then Loan Repayment schedule has 10 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2026 | 01 January 2026 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2026 | 10 March 2026 | 650.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 100.0 | 0.0 | 100.0 | 0.0 | + | 3 | 28 | 01 March 2026 | 10 March 2026 | 650.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 9 | 10 March 2026 | 10 March 2026 | 662.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 10 April 2026 | | 551.67 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 6 | 30 | 10 May 2026 | | 441.34 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 7 | 31 | 10 June 2026 | | 331.01 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 8 | 30 | 10 July 2026 | | 220.68 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 9 | 31 | 10 August 2026 | | 110.35 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | 0.0 | 0.0 | 0.0 | 110.33 | + | 10 | 31 | 10 September 2026 | | 0.0 | 110.35 | 0.0 | 0.0 | 0.0 | 110.35 | 0.0 | 0.0 | 0.0 | 110.35 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1012.0 | 0.0 | 0.0 | 0.0 | 1012.0 | 350.0 | 0.0 | 100.0 | 662.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2026 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | + | 01 January 2026 | Down Payment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false | + | 10 March 2026 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false | + | 10 March 2026 | Chargeback | 12.0 | 12.0 | 0.0 | 0.0 | 0.0 | 662.0 | false | false | + | 10 March 2026 | Re-age | 662.0 | 662.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "10 March 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C70229 @AdvancedPaymentAllocation + Scenario: Verify that Loan re-aging trn with backdated repayment and chargeback - N+1 installment after maturity date overlaps with re-aging with equal amortization; outstanding full interest - UC4 + When Admin sets the business date to "01 January 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2025 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2025" with "1000" amount and expected disbursement date on "01 January 2025" + When Admin successfully disburse the loan on "01 January 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 31 | 01 February 2025 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 28 | 01 March 2025 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 31 | 01 April 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | +# --- add charge a month later --- # + When Admin sets the business date to "3 May 2025" + And Customer makes "AUTOPAY" repayment on "01 March 2025" with 250 EUR transaction amount + When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 125 EUR transaction amount + Then Loan Repayment schedule has 5 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | 01 March 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0| 0.0 | 250.0| 0.0 | + | 2 | 31 | 01 February 2025 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 3 | 28 | 01 March 2025 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 4 | 31 | 01 April 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 5 | 32 | 03 May 2025 | | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1125.0 | 0.0 | 0.0 | 0.0 | 1125.0 | 250.0 | 0.0 | 250.0 | 875.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | + | 01 March 2025 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | + | 03 May 2025 | Chargeback | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 875.0 | false | +# --- add re-aging trn with start date as maturity date --- # + When Admin creates a Loan re-aging transaction by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | WEEKS | 01 April 2025 | 6 | EQUAL_AMORTIZATION_FULL_INTEREST | + Then Loan Repayment schedule has 10 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | 01 March 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 250.0 | 0.0 | + | 2 | 31 | 01 February 2025 | 01 April 2025 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 28 | 01 March 2025 | 01 April 2025 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 01 April 2025 | 01 April 2025 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 0 | 01 April 2025 | | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 6 | 7 | 08 April 2025 | | 500.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 7 | 7 | 15 April 2025 | | 375.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 8 | 7 | 22 April 2025 | | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 9 | 7 | 29 April 2025 | | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | + | 10 | 7 | 06 May 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1125.0 | 0.0 | 0.0 | 0.0 | 1125.0 | 250.0 | 0.0 | 250.0| 875.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | + | 01 March 2025 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | + | 03 May 2025 | Chargeback | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 875.0 | false | + | 01 April 2025 | Re-age | 750.0 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | + When Loan Pay-off is made on "03 May 2025" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingPreview.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingPreview.feature index 0e7215425e3..94c70114cdb 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingPreview.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAgingPreview.feature @@ -2666,4 +2666,344 @@ Feature: LoanReAgingPreview When Loan Pay-off is made on "01 April 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met + @TestRailId:C4690 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging trn preview and actual repayment schedule - interest bearing loan with equal amortization; outstanding payable interest - UC1 + When Admin sets the business date to "12 September 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_NO_INTEREST_RECALC_REFUND_FULL | 12 September 2025 | 1200 | 9.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "12 September 2025" with "1200" amount and expected disbursement date on "12 September 2025" + When Admin successfully disburse the loan on "12 September 2025" with "1200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | | 1003.99 | 196.01 | 9.85 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 2 | 31 | 12 November 2025 | | 806.65 | 197.34 | 8.52 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 3 | 30 | 12 December 2025 | | 607.41 | 199.24 | 6.62 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 4 | 31 | 12 January 2026 | | 406.7 | 200.71 | 5.15 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 5 | 31 | 12 February 2026 | | 204.29 | 202.41 | 3.45 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 6 | 28 | 12 March 2026 | | 0.0 | 204.29 | 1.57 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 35.16 | 0.0 | 0.0 | 1235.16 | 0.0 | 0.0 | 0.0 | 1235.16 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + When Admin sets the business date to "12 October 2025" + And Customer makes "AUTOPAY" repayment on "12 October 2025" with 200 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | | 1003.99 | 196.01 | 9.85 | 0.0 | 0.0 | 205.86 | 200.0 | 0.0 | 0.0 | 5.86 | + | 2 | 31 | 12 November 2025 | | 806.65 | 197.34 | 8.52 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 3 | 30 | 12 December 2025 | | 607.41 | 199.24 | 6.62 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 4 | 31 | 12 January 2026 | | 406.7 | 200.71 | 5.15 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 5 | 31 | 12 February 2026 | | 204.29 | 202.41 | 3.45 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + | 6 | 28 | 12 March 2026 | | 0.0 | 204.29 | 1.57 | 0.0 | 0.0 | 205.86 | 0.0 | 0.0 | 0.0 | 205.86 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 35.16 | 0.0 | 0.0 | 1235.16 | 200.0 | 0.0 | 0.0 | 1035.16 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + | 12 October 2025 | Repayment | 200.0 | 196.01 | 3.99 | 0.0 | 0.0 | 1003.99 | false | false | + When Admin sets the business date to "12 February 2026" + When Admin runs inline COB job for Loan + When Admin creates a Loan re-aging preview by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 12 February 2026 | 10 | EQUAL_AMORTIZATION_PAYABLE_INTEREST | + Then Loan Re-Aged Repayment schedule preview has 15 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | 12 February 2026 | 1003.99 | 196.01 | 3.99 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 12 November 2025 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 12 December 2025 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 12 January 2026 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 12 February 2026 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 6 | 0 | 12 February 2026 | | 903.59 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 7 | 28 | 12 March 2026 | | 803.19 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 8 | 31 | 12 April 2026 | | 702.79 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 9 | 30 | 12 May 2026 | | 602.39 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 10 | 31 | 12 June 2026 | | 501.99 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 11 | 30 | 12 July 2026 | | 401.59 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 12 | 31 | 12 August 2026 | | 301.19 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 13 | 31 | 12 September 2026 | | 200.79 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 14 | 30 | 12 October 2026 | | 100.39 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 15 | 31 | 12 November 2026 | | 0.0 | 100.39 | 3.35 | 0.0 | 0.0 | 103.74 | 0.0 | 0.0 | 0.0 | 103.74 | + Then Loan Re-Aged Repayment schedule preview has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 37.58 | 0.0 | 0.0 | 1237.58 | 200.0 | 0.0 | 0.0 | 1037.58 | + When Admin creates a Loan re-aging transaction by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 12 February 2026 | 10 | EQUAL_AMORTIZATION_PAYABLE_INTEREST | + Then Loan Repayment schedule has 15 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | 12 February 2026 | 1003.99 | 196.01 | 3.99 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 12 November 2025 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 12 December 2025 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 12 January 2026 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 12 February 2026 | 12 February 2026 | 1003.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 6 | 0 | 12 February 2026 | | 903.59 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 7 | 28 | 12 March 2026 | | 803.19 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 8 | 31 | 12 April 2026 | | 702.79 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 9 | 30 | 12 May 2026 | | 602.39 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 10 | 31 | 12 June 2026 | | 501.99 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 11 | 30 | 12 July 2026 | | 401.59 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 12 | 31 | 12 August 2026 | | 301.19 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 13 | 31 | 12 September 2026 | | 200.79 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 14 | 30 | 12 October 2026 | | 100.39 | 100.4 | 3.36 | 0.0 | 0.0 | 103.76 | 0.0 | 0.0 | 0.0 | 103.76 | + | 15 | 31 | 12 November 2026 | | 0.0 | 100.39 | 3.35 | 0.0 | 0.0 | 103.74 | 0.0 | 0.0 | 0.0 | 103.74 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 37.58 | 0.0 | 0.0 | 1237.58 | 200.0 | 0.0 | 0.0 | 1037.58 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + | 12 October 2025 | Repayment | 200.0 | 196.01 | 3.99 | 0.0 | 0.0 | 1003.99 | false | false | + | 11 February 2026 | Accrual | 33.48 | 0.0 | 33.48 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2026 | Re-age | 1037.58 | 1003.99 | 33.59 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "12 February 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4691 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging trn preview and actual repayment schedule - interest bearing loan with equal amortization; outstanding FULL interest - UC2 + When Admin sets the business date to "12 October 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL_ZERO_CHARGE_OFF | 12 September 2025 | 1200 | 9.99 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "12 September 2025" with "1200" amount and expected disbursement date on "12 September 2025" + When Admin successfully disburse the loan on "12 September 2025" with "1200" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | | 1004.12 | 195.88 | 9.99 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 2 | 31 | 12 November 2025 | | 806.61 | 197.51 | 8.36 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 3 | 30 | 12 December 2025 | | 607.46 | 199.15 | 6.72 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 4 | 31 | 12 January 2026 | | 406.65 | 200.81 | 5.06 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 5 | 31 | 12 February 2026 | | 204.17 | 202.48 | 3.39 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 6 | 28 | 12 March 2026 | | 0.0 | 204.17 | 1.7 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 35.22 | 0.0 | 0.0 | 1235.22 | 0.0 | 0.0 | 0.0 | 1235.22 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + When Admin sets the business date to "12 October 2025" + And Customer makes "AUTOPAY" repayment on "12 October 2025" with 200 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | | 1004.12 | 195.88 | 9.99 | 0.0 | 0.0 | 205.87 | 200.0 | 0.0 | 0.0 | 5.87 | + | 2 | 31 | 12 November 2025 | | 806.61 | 197.51 | 8.36 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 3 | 30 | 12 December 2025 | | 607.46 | 199.15 | 6.72 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 4 | 31 | 12 January 2026 | | 406.65 | 200.81 | 5.06 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 5 | 31 | 12 February 2026 | | 204.17 | 202.48 | 3.39 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + | 6 | 28 | 12 March 2026 | | 0.0 | 204.17 | 1.7 | 0.0 | 0.0 | 205.87 | 0.0 | 0.0 | 0.0 | 205.87 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 35.22 | 0.0 | 0.0 | 1235.22 | 200.0 | 0.0 | 0.0 | 1035.22 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + | 12 October 2025 | Repayment | 200.0 | 190.01 | 9.99 | 0.0 | 0.0 | 1009.99 | false | false | + When Admin sets the business date to "24 February 2026" + When Admin runs inline COB job for Loan + When Admin creates a Loan re-aging preview by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 12 February 2026 | 10 | EQUAL_AMORTIZATION_FULL_INTEREST | + Then Loan Re-Aged Repayment schedule preview has 15 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | 12 February 2026 | 1009.99 | 190.01 | 9.99 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 12 November 2025 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 12 December 2025 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 12 January 2026 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 12 February 2026 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 6 | 0 | 12 February 2026 | | 908.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 7 | 28 | 12 March 2026 | | 807.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 8 | 31 | 12 April 2026 | | 706.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 9 | 30 | 12 May 2026 | | 605.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 10 | 31 | 12 June 2026 | | 504.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 11 | 30 | 12 July 2026 | | 403.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 12 | 31 | 12 August 2026 | | 302.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 13 | 31 | 12 September 2026 | | 201.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 14 | 30 | 12 October 2026 | | 100.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 15 | 31 | 12 November 2026 | | 0.0 | 100.99 | 3.56 | 0.0 | 0.0 | 104.55 | 0.0 | 0.0 | 0.0 | 104.55 | + Then Loan Re-Aged Repayment schedule preview has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 45.41 | 0.0 | 0.0 | 1245.41 | 200.0 | 0.0 | 0.0 | 1045.41 | + When Admin creates a Loan re-aging transaction by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | reAgeInterestHandling | + | 1 | MONTHS | 12 February 2026 | 10 | EQUAL_AMORTIZATION_FULL_INTEREST | + Then Loan Repayment schedule has 15 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 12 September 2025 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 12 October 2025 | 12 February 2026 | 1009.99 | 190.01 | 9.99 | 0.0 | 0.0 | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 12 November 2025 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 12 December 2025 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 12 January 2026 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 5 | 31 | 12 February 2026 | 12 February 2026 | 1009.99 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 6 | 0 | 12 February 2026 | | 908.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 7 | 28 | 12 March 2026 | | 807.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 8 | 31 | 12 April 2026 | | 706.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 9 | 30 | 12 May 2026 | | 605.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 10 | 31 | 12 June 2026 | | 504.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 11 | 30 | 12 July 2026 | | 403.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 12 | 31 | 12 August 2026 | | 302.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 13 | 31 | 12 September 2026 | | 201.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 14 | 30 | 12 October 2026 | | 100.99 | 101.0 | 3.54 | 0.0 | 0.0 | 104.54 | 0.0 | 0.0 | 0.0 | 104.54 | + | 15 | 31 | 12 November 2026 | | 0.0 | 100.99 | 3.56 | 0.0 | 0.0 | 104.55 | 0.0 | 0.0 | 0.0 | 104.55 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 45.41 | 0.0 | 0.0 | 1245.41 | 200.0 | 0.0 | 0.0 | 1045.41 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 12 September 2025 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | false | false | + | 12 October 2025 | Repayment | 200.0 | 190.01 | 9.99 | 0.0 | 0.0 | 1009.99 | false | false | + | 12 October 2025 | Accrual Activity | 9.99 | 0.0 | 9.99 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2026 | Accrual Activity | 3.54 | 0.0 | 3.54 | 0.0 | 0.0 | 0.0 | false | true | + | 12 February 2026 | Re-age | 1045.41 | 1009.99 | 35.42 | 0.0 | 0.0 | 0.0 | false | false | + | 23 February 2026 | Accrual | 36.82 | 0.0 | 36.82 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "24 February 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + @TestRailId:C4692 @AdvancedPaymentAllocation + Scenario: Re-aging preview should not accumulate 1-day periods after repeated re-aging across month-end + When Admin sets the business date to "28 January 2026" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 28 January 2026 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "28 January 2026" with "100" amount and expected disbursement date on "28 January 2026" + When Admin successfully disburse the loan on "28 January 2026" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 28 February 2026 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 28 March 2026 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 28 April 2026 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 28 May 2026 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 28 June 2026 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 28 July 2026 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- first re-age on disbursement date 28 January 2026 --- # + When Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 28 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 28 February 2026 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 28 | 28 March 2026 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 31 | 28 April 2026 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 30 | 28 May 2026 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 31 | 28 June 2026 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | +# --- second re-age on 29 January 2026: schedule accumulates zeroed period boundary --- # + When Admin sets the business date to "29 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 1 | 29 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 30 | 28 February 2026 | | 83.59 | 16.41 | 0.6 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 28 | 28 March 2026 | | 67.07 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 31 | 28 April 2026 | | 50.45 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 30 | 28 May 2026 | | 33.73 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 31 | 28 June 2026 | | 16.92 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.92 | 0.1 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.07 | 0.0 | 0.0 | 102.07 | 0.0 | 0.0 | 0.0 | 102.07 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | +# --- third re-age on 30 January 2026 --- # + When Admin sets the business date to "30 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 2 | 30 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 28 February 2026 | | 83.61 | 16.39 | 0.62 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 28 | 28 March 2026 | | 67.09 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 31 | 28 April 2026 | | 50.47 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 30 | 28 May 2026 | | 33.75 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 31 | 28 June 2026 | | 16.94 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.94 | 0.1 | 0.0 | 0.0 | 17.04 | 0.0 | 0.0 | 0.0 | 17.04 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.09 | 0.0 | 0.0 | 102.09 | 0.0 | 0.0 | 0.0 | 102.09 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2026 | Re-age | 100.04 | 100.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | +# --- fourth re-age on 31 January 2026 --- # + When Admin sets the business date to "31 January 2026" + And Admin creates a Loan re-aging transaction with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 3 | 31 January 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 28 | 28 February 2026 | | 83.62 | 16.38 | 0.64 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 3 | 28 | 28 March 2026 | | 67.09 | 16.53 | 0.49 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 4 | 31 | 28 April 2026 | | 50.46 | 16.63 | 0.39 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 5 | 30 | 28 May 2026 | | 33.73 | 16.73 | 0.29 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 6 | 31 | 28 June 2026 | | 16.91 | 16.82 | 0.2 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.91 | 0.1 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.11 | 0.0 | 0.0 | 102.11 | 0.0 | 0.0 | 0.0 | 102.11 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 28 January 2026 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 28 January 2026 | Re-age | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2026 | Re-age | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2026 | Re-age | 100.04 | 100.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 31 January 2026 | Re-age | 100.06 | 100.0 | 0.06 | 0.0 | 0.0 | 0.0 | false | false | +# --- re-age preview on 01 February 2026: must return exactly 6 new periods, NOT 12 with 1-day periods --- # + When Admin sets the business date to "01 February 2026" + And Admin creates a Loan re-aging preview by Loan external ID with the following data: + | frequencyNumber | frequencyType | startDate | numberOfInstallments | + | 1 | MONTHS | 28 February 2026 | 6 | + Then Loan Re-Aged Repayment schedule preview has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 28 January 2026 | | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | | | | + | 1 | 4 | 01 February 2026 | 28 January 2026 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 2 | 27 | 28 February 2026 | | 83.62 | 16.38 | 0.64 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 3 | 28 | 28 March 2026 | | 67.09 | 16.53 | 0.49 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 4 | 31 | 28 April 2026 | | 50.46 | 16.63 | 0.39 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 5 | 30 | 28 May 2026 | | 33.73 | 16.73 | 0.29 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 6 | 31 | 28 June 2026 | | 16.91 | 16.82 | 0.2 | 0.0 | 0.0 | 17.02 | 0.0 | 0.0 | 0.0 | 17.02 | + | 7 | 30 | 28 July 2026 | | 0.0 | 16.91 | 0.1 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + Then Loan Re-Aged Repayment schedule preview has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.11 | 0.0 | 0.0 | 102.11 | 0.0 | 0.0 | 0.0 | 102.11 | + When Loan Pay-off is made on "01 February 2026" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature index efdff43e2b9..411a186655d 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature @@ -87,7 +87,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 25 January 2024 | Re-amortize | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -136,7 +136,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | false | | 25 January 2024 | Re-amortize | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | true | Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2024-01-19" - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "26 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -156,7 +156,7 @@ Feature: LoanReAmortization When Admin sets the business date to "25 January 2024" When Admin creates a Loan re-amortization transaction on current business date Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -189,7 +189,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 25 January 2024 | Re-amortize | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -220,7 +220,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 01 February 2024 | Re-amortize | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "01 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -269,7 +269,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 15 January 2024 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | | 01 February 2024 | Re-amortize | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "02 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -302,7 +302,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 01 February 2024 | Re-amortize | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "01 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -335,7 +335,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 31 January 2024 | Re-amortize | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "31 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -384,7 +384,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 17 January 2024 | Repayment | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 325.0 | | 30 January 2024 | Re-amortize | 75.0 | 75.0 | 0.0 | 0.0 | 0.0 | 0.0 | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "30 January 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -405,7 +405,7 @@ Feature: LoanReAmortization When Admin creates a Loan re-amortization transaction on current business date Then LoanDelinquencyRangeChangeBusinessEvent is created Then LoanReAmortizeBusinessEvent is created - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "01 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -463,7 +463,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 600.0 | false | false | | 16 January 2024 | Repayment | 120.0 | 120.0 | 0.0 | 0.0 | 0.0 | 480.0 | true | false | | 20 February 2024 | Re-amortize | 360.0 | 360.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -518,7 +518,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 600.0 | false | false | | 16 January 2024 | Repayment | 120.0 | 120.0 | 0.0 | 0.0 | 0.0 | 480.0 | false | false | | 20 February 2024 | Re-amortize | 240.0 | 240.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -576,7 +576,7 @@ Feature: LoanReAmortization | 16 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | | 16 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 675.0 | false | false | | 20 February 2024 | Re-amortize | 398.0 | 398.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -634,7 +634,7 @@ Feature: LoanReAmortization | 01 January 2024 | Down Payment | 200.0 | 200.0 | 0.0 | 0.0 | 0.0 | 600.0 | false | false | | 16 January 2024 | Repayment | 140.0 | 120.0 | 0.0 | 0.0 | 20.0 | 480.0 | false | true | | 20 February 2024 | Re-amortize | 240.0 | 240.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "25 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -740,7 +740,7 @@ Feature: LoanReAmortization | 20 February 2024 | Re-amortize | 360.0 | 360.0 | 0.0 | 0.0 | 0.0 | 0.0 | true | false | | 21 February 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 700.0 | false | false | | 21 February 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 675.0 | true | false | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "23 February 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -1215,7 +1215,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | - # --- close the loan --- # +# --- close the loan --- # When Loan Pay-off is made on "15 March 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met @@ -1284,9 +1284,8 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | - #TODO - Active status after pay-off - #When Loan Pay-off is made on "15 March 2024" - #Then Loan is closed with zero outstanding balance and it's all installments have obligations met + When Loan Pay-off is made on "15 March 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met @TestRailId:C4221 @AdvancedPaymentAllocation Scenario: Verify Re-amortization transaction on interest bearing loan - Interest handling: EQUAL_AMORTIZATION_INTEREST_SPLIT - UC3: Principal interest and fee re-amortization, N+1 installment @@ -1686,7 +1685,6 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | - When Admin sets the business date to "01 April 2024" And Customer makes "AUTOPAY" repayment on "01 March 2024" with 17.01 EUR transaction amount Then Loan Repayment schedule has 6 periods, with the following data for periods: @@ -4383,7 +4381,7 @@ Feature: LoanReAmortization Then Loan is closed with zero outstanding balance and it's all installments have obligations met @TestRailId:C4503 - Scenario: Verify Re-amortization with backdated interest pause to re-aging, starts after re-aging with last instalment strategy - interest bearing loan with equal amortization + interest split - UC3.3 + Scenario: Verify Re-amortization with backdated interest pause to re-aging, starts after re-amortization with last instalment strategy - interest bearing loan with equal amortization + interest split - UC3.3 When Admin sets the business date to "01 January 2024" And Admin creates a client with random data And Admin creates a fully customized loan with the following data: @@ -4425,7 +4423,7 @@ Feature: LoanReAmortization | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | -# --- Check before re-age --- # +# --- Check before re-amortization --- # When Admin sets the business date to "15 March 2024" Then Loan Repayment schedule has 6 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | @@ -4818,13 +4816,13 @@ Feature: LoanReAmortization | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | | 69.84 | 13.73 | 0.57 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 4 | 30 | 01 May 2024 | | 56.03 | 13.81 | 0.49 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 5 | 31 | 01 June 2024 | | 42.14 | 13.89 | 0.41 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 6 | 30 | 01 July 2024 | | 28.17 | 13.97 | 0.33 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 7 | 31 | 01 August 2024 | | 14.11 | 14.06 | 0.24 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 8 | 31 | 01 September 2024 | | 0.0 | 14.11 | 0.16 | 0.0 | 0.0 | 14.27 | 0.0 | 0.0 | 0.0 | 14.27 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 69.85 | 13.72 | 0.57 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 4 | 30 | 01 May 2024 | | 56.05 | 13.8 | 0.49 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 5 | 31 | 01 June 2024 | | 42.17 | 13.88 | 0.41 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 6 | 30 | 01 July 2024 | | 28.21 | 13.96 | 0.33 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 7 | 31 | 01 August 2024 | | 14.16 | 14.05 | 0.24 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 8 | 31 | 01 September 2024 | | 0.0 | 14.16 | 0.16 | 0.0 | 0.0 | 14.32 | 0.0 | 0.0 | 0.0 | 14.32 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 100.0 | 2.78 | 0.0 | 0.0 | 102.78 | 17.01 | 0.0 | 0.0 | 85.77 | @@ -4842,15 +4840,15 @@ Feature: LoanReAmortization | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | | 69.84 | 13.73 | 0.57 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 4 | 30 | 01 May 2024 | | 56.03 | 13.81 | 0.49 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 5 | 31 | 01 June 2024 | | 42.14 | 13.89 | 0.41 | 0.0 | 0.0 | 14.3 | 0.0 | 0.0 | 0.0 | 14.3 | - | 6 | 30 | 01 July 2024 | | 33.84 | 8.3 | 0.33 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | - | 7 | 31 | 01 August 2024 | | 25.49 | 8.35 | 0.28 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | - | 8 | 31 | 01 September 2024 | | 17.09 | 8.4 | 0.23 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | - | 9 | 30 | 01 October 2024 | | 8.56 | 8.53 | 0.1 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | - | 10 | 31 | 01 November 2024 | | 0.0 | 8.56 | 0.05 | 0.0 | 0.0 | 8.61 | 0.0 | 0.0 | 0.0 | 8.61 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 69.85 | 13.72 | 0.57 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 4 | 30 | 01 May 2024 | | 56.05 | 13.8 | 0.49 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 5 | 31 | 01 June 2024 | | 42.17 | 13.88 | 0.41 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 6 | 30 | 01 July 2024 | | 33.87 | 8.3 | 0.33 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 7 | 31 | 01 August 2024 | | 25.52 | 8.35 | 0.28 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 8 | 31 | 01 September 2024 | | 17.12 | 8.4 | 0.23 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 9 | 30 | 01 October 2024 | | 8.59 | 8.53 | 0.1 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 10 | 31 | 01 November 2024 | | 0.0 | 8.59 | 0.05 | 0.0 | 0.0 | 8.64 | 0.0 | 0.0 | 0.0 | 8.64 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 100.0 | 3.04 | 0.0 | 0.0 | 103.04 | 17.01 | 0.0 | 0.0 | 86.03 | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationAccruals.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationAccruals.feature index 31e41a76759..62e0563b04a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationAccruals.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortizationAccruals.feature @@ -1050,7 +1050,7 @@ Feature: LoanReAmortizationAccruals When Loan Pay-off is made on "01 April 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met - @TestRailId:С4607 @AdvancedPaymentAllocation + @TestRailId:C4607 @AdvancedPaymentAllocation Scenario: Verify Re-amortization on interest bearing loan - Default Behavior - Accrual and Accrual Activity handling, DownPayment Scenario When Admin sets the business date to "01 January 2024" When Admin creates a client with random data @@ -2569,3 +2569,1929 @@ Feature: LoanReAmortizationAccruals | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 21 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4628 @AdvancedPaymentAllocation + Scenario: Verify re-amortization with equal outstanding interest split - accrual & accrual Activity S1 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ACCRUAL_ACTIVITY_POSTING | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + # --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + # --- re-amortization transaction + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.2 | 0.0 | 1.2 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "16 March 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "01 April 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + And Customer makes "AUTOPAY" repayment on "01 April 2024" with 21.32 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 21.32 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 38.33 | 0.0 | 0.0 | 63.96 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Repayment | 21.32 | 20.71 | 0.61 | 0.0 | 0.0 | 62.86 | false | false | + When Admin sets the business date to "01 May 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 24 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 28 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 29 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 30 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "01 May 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 May 2024 | Accrual | 0.26 | 0.0 | 0.26 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan has 1.92 total Accruals + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Repayment | 21.32 | 20.71 | 0.61 | 0.0 | 0.0 | 62.86 | false | false | + | 01 April 2024 | Accrual Activity | 0.61 | 0.0 | 0.61 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Repayment | 63.59 | 62.86 | 0.73 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Accrual Activity | 0.73 | 0.0 | 0.73 | 0.0 | 0.0 | 0.0 | false | false | + + @TestRailId:C4629 @AdvancedPaymentAllocation + Scenario: Verify re-amortization with equal outstanding interest split - accrual & accrual Activity S2 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ACCRUAL_ACTIVITY_POSTING | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 75.21 | 24.79 | 0.58 | 0.0 | 0.0 | 25.37 | 0.0 | 0.0 | 0.0 | 25.37 | + | 2 | 29 | 01 March 2024 | | 50.28 | 24.93 | 0.44 | 0.0 | 0.0 | 25.37 | 0.0 | 0.0 | 0.0 | 25.37 | + | 3 | 31 | 01 April 2024 | | 25.2 | 25.08 | 0.29 | 0.0 | 0.0 | 25.37 | 0.0 | 0.0 | 0.0 | 25.37 | + | 4 | 30 | 01 May 2024 | | 0.0 | 25.2 | 0.15 | 0.0 | 0.0 | 25.35 | 0.0 | 0.0 | 0.0 | 25.35 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.46 | 0.0 | 0.0 | 101.46 | 0.0 | 0.0 | 0.0 | 101.46 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + # --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 25.37 EUR transaction amount + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 75.21 | 24.79 | 0.58 | 0.0 | 0.0 | 25.37 | 25.37 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 50.28 | 24.93 | 0.44 | 0.0 | 0.0 | 25.37 | 0.0 | 0.0 | 0.0 | 25.37 | + | 3 | 31 | 01 April 2024 | | 25.2 | 25.08 | 0.29 | 0.0 | 0.0 | 25.37 | 0.0 | 0.0 | 0.0 | 25.37 | + | 4 | 30 | 01 May 2024 | | 0.0 | 25.2 | 0.15 | 0.0 | 0.0 | 25.35 | 0.0 | 0.0 | 0.0 | 25.35 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.46 | 0.0 | 0.0 | 101.46 | 25.37 | 0.0 | 0.0 | 76.09 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 25.37 | 24.79 | 0.58 | 0.0 | 0.0 | 75.21 | false | false | + # --- re-amortization transaction + When Admin sets the business date to "01 April 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.44 | 0.0 | 1.44 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 75.21 | 24.79 | 0.58 | 0.0 | 0.0 | 25.37 | 25.37 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 April 2024 | 75.21 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 75.21 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | | 0.0 | 75.21 | 1.32 | 0.0 | 0.0 | 76.53 | 0.0 | 0.0 | 0.0 | 76.53 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.9 | 0.0 | 0.0 | 101.9 | 25.37 | 0.0 | 0.0 | 76.53 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 25.37 | 24.79 | 0.58 | 0.0 | 0.0 | 75.21 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Re-amortize | 50.74 | 49.86 | 0.88 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "02 April 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 April 2024 | Accrual Adjustment | 0.86 | 0.0 | 0.86 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "01 May 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 02 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 16 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 17 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 18 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 19 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 20 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 21 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 23 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 24 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 25 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 26 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 27 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 28 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 29 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 30 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "02 May 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 May 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "02 May 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + Then Loan has 1.90 total Accruals + + @TestRailId:C4630 @AdvancedPaymentAllocation + Scenario: Verify Re-amortization trn with interest and fee re-amortization - interest bearing loan with equal amortization + interest split: Accrual and Accrual Activity S3 + When Admin sets the business date to "01 January 2024" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- Repayment on due date and snooze fee added --- + When Admin sets the business date to "01 February 2024" + When Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + And Admin adds "LOAN_SNOOZE_FEE" due date charge with "15 February 2024" due date and 10 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 10.0 | 0.0 | 27.01 | 0.0 | 0.0 | 0.0 | 27.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 10.0 | 0.0 | 112.05 | 17.01 | 0.0 | 0.0 | 95.04 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- Re-amortization transaction --- + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 2.5 | 0.0 | 23.82 | 0.0 | 0.0 | 0.0 | 23.82 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 2.5 | 0.0 | 23.82 | 0.0 | 0.0 | 0.0 | 23.82 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 2.5 | 0.0 | 23.82 | 0.0 | 0.0 | 0.0 | 23.82 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 2.5 | 0.0 | 23.82 | 0.0 | 0.0 | 0.0 | 23.82 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 10.0 | 0.0 | 112.29 | 17.01 | 0.0 | 0.0 | 95.28 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 February 2024 | Accrual | 10.02 | 0.0 | 0.02 | 10.0 | 0.0 | 0.0 | false | false | + | 16 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 23 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 25 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 26 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "15 March 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4631 @AdvancedPaymentAllocation + Scenario: Verify Re-amortization trn with N+1 installment - interest bearing loan with equal amortization + interest split: Accrual and Accrual Activity S4 + When Admin sets the business date to "01 January 2024" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- Repayment on due date and snooze fee added --- + When Admin sets the business date to "01 February 2024" + When Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + And Admin adds "LOAN_SNOOZE_FEE" due date charge with "15 July 2024" due date and 10 EUR transaction amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + | 7 | 14 | 15 July 2024 | | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 10.0 | 0.0 | 112.05 | 17.01 | 0.0 | 0.0 | 95.04 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- Re-amortization transaction --- + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.47 | 16.58 | 0.43 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.75 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.94 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.94 | 0.1 | 0.0 | 0.0 | 17.04 | 0.0 | 0.0 | 0.0 | 17.04 | + | 7 | 14 | 15 July 2024 | | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.09 | 10.0 | 0.0 | 112.09 | 17.01 | 0.0 | 0.0 | 95.08 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual Activity | 0.49 | 0.0 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 23 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 25 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 26 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 7 | 14 | 15 July 2024 | | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 10.0 | 0.0 | 112.29 | 17.01 | 0.0 | 0.0 | 95.28 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "15 March 2024" + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Repayment | 94.27 | 83.57 | 0.7 | 10.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Activity | 10.7 | 0.0 | 0.7 | 10.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 March 2024 | Accrual | 10.01 | 0.0 | 0.01 | 10.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4632 @AdvancedPaymentAllocation + Scenario: Verify Re-amortization trn with downpayment - interest bearing loan with equal amortization + interest split: Accrual and Accrual Activity S5 + When Admin sets the business date to "01 January 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_AUTO_DOWNPAYMENT | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 75.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2024 | | 62.68 | 12.32 | 0.44 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 3 | 29 | 01 March 2024 | | 50.29 | 12.39 | 0.37 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 4 | 31 | 01 April 2024 | | 37.82 | 12.47 | 0.29 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 5 | 30 | 01 May 2024 | | 25.28 | 12.54 | 0.22 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 6 | 31 | 01 June 2024 | | 12.67 | 12.61 | 0.15 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 7 | 30 | 01 July 2024 | | 0.0 | 12.67 | 0.07 | 0.0 | 0.0 | 12.74 | 0.0 | 0.0 | 0.0 | 12.74 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.54 | 0.0 | 0.0 | 101.54 | 25.0 | 0.0 | 0.0 | 76.54 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | false | +# --- Repayment on due date --- + When Admin sets the business date to "01 February 2024" + When Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 12.76 EUR transaction amount + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 75.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2024 | 01 February 2024 | 62.68 | 12.32 | 0.44 | 0.0 | 0.0 | 12.76 | 12.76 | 0.0 | 0.0 | 0.0 | + | 3 | 29 | 01 March 2024 | | 50.29 | 12.39 | 0.37 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 4 | 31 | 01 April 2024 | | 37.82 | 12.47 | 0.29 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 5 | 30 | 01 May 2024 | | 25.28 | 12.54 | 0.22 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 6 | 31 | 01 June 2024 | | 12.67 | 12.61 | 0.15 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 7 | 30 | 01 July 2024 | | 0.0 | 12.67 | 0.07 | 0.0 | 0.0 | 12.74 | 0.0 | 0.0 | 0.0 | 12.74 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.54 | 0.0 | 0.0 | 101.54 | 37.76 | 0.0 | 0.0 | 63.78 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | false | + | 31 January 2024 | Accrual | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 12.76 | 12.32 | 0.44 | 0.0 | 0.0 | 62.68 | false | false | +# --- Re-amortization transaction --- + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 75.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2024 | 01 February 2024 | 62.68 | 12.32 | 0.44 | 0.0 | 0.0 | 12.76 | 12.76 | 0.0 | 0.0 | 0.0 | + | 3 | 29 | 01 March 2024 | | 50.29 | 12.39 | 0.37 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 4 | 31 | 01 April 2024 | | 37.86 | 12.43 | 0.33 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 5 | 30 | 01 May 2024 | | 25.32 | 12.54 | 0.22 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 6 | 31 | 01 June 2024 | | 12.71 | 12.61 | 0.15 | 0.0 | 0.0 | 12.76 | 0.0 | 0.0 | 0.0 | 12.76 | + | 7 | 30 | 01 July 2024 | | 0.0 | 12.71 | 0.07 | 0.0 | 0.0 | 12.78 | 0.0 | 0.0 | 0.0 | 12.78 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.58 | 0.0 | 0.0 | 101.58 | 37.76 | 0.0 | 0.0 | 63.82 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | false | + | 01 February 2024 | Repayment | 12.76 | 12.32 | 0.44 | 0.0 | 0.0 | 62.68 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 31 January 2024 | Accrual | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 03 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 05 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 06 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 09 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 11 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 14 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 15 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 19 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 20 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 21 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 23 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 26 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 28 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 7 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2024 | 01 January 2024 | 75.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 February 2024 | 01 February 2024 | 62.68 | 12.32 | 0.44 | 0.0 | 0.0 | 12.76 | 12.76 | 0.0 | 0.0 | 0.0 | + | 3 | 29 | 01 March 2024 | 15 March 2024 | 62.68 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 4 | 31 | 01 April 2024 | | 47.15 | 15.53 | 0.46 | 0.0 | 0.0 | 15.99 | 0.0 | 0.0 | 0.0 | 15.99 | + | 5 | 30 | 01 May 2024 | | 31.53 | 15.62 | 0.37 | 0.0 | 0.0 | 15.99 | 0.0 | 0.0 | 0.0 | 15.99 | + | 6 | 31 | 01 June 2024 | | 15.81 | 15.72 | 0.27 | 0.0 | 0.0 | 15.99 | 0.0 | 0.0 | 0.0 | 15.99 | + | 7 | 30 | 01 July 2024 | | 0.0 | 15.81 | 0.18 | 0.0 | 0.0 | 15.99 | 0.0 | 0.0 | 0.0 | 15.99 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.72 | 0.0 | 0.0 | 101.72 | 37.76 | 0.0 | 0.0 | 63.96 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | false | + | 01 February 2024 | Repayment | 12.76 | 12.32 | 0.44 | 0.0 | 0.0 | 62.68 | false | false | + | 15 March 2024 | Re-amortize | 12.76 | 12.39 | 0.37 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "15 March 2024" + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 January 2024 | Down Payment | 25.0 | 25.0 | 0.0 | 0.0 | 0.0 | 75.0 | false | false | + | 01 February 2024 | Repayment | 12.76 | 12.32 | 0.44 | 0.0 | 0.0 | 62.68 | false | false | + | 15 March 2024 | Re-amortize | 12.76 | 12.39 | 0.37 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Repayment | 63.21 | 62.68 | 0.53 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4633 @AdvancedPaymentAllocation + Scenario: Verify Re-amortization trn reverse-replayed by backdated repayment - interest bearing loan with equal amortization + interest split: Accrual and Accrual Activity S6 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- Re-amortization transaction --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + When Admin sets the business date to "01 April 2024" + When Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "01 March 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 34.02 | 0.0 | 0.0 | 68.03 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual Activity | 0.49 | 0.0 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | +# --- close the loan --- # + When Loan Pay-off is made on "01 April 2024" + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual Activity | 0.49 | 0.0 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false | + | 01 April 2024 | Repayment | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual Activity | 0.39 | 0.0 | 0.39 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 April 2024 | Accrual | 0.29 | 0.0 | 0.29 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4634 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging trn with 2nd disb after re-amortization - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S7 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE | 01 January 2024 | 200 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "200" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- re-amortization transaction --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | +# --- 2nd disbursement --- # + When Admin sets the business date to "15 April 2024" + When Admin runs inline COB job for Loan + When Admin successfully disburse the loan on "15 April 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | | | 15 April 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 4 | 30 | 01 May 2024 | | 108.76 | 54.1 | 0.85 | 0.0 | 0.0 | 54.95 | 0.0 | 0.0 | 0.0 | 54.95 | + | 5 | 31 | 01 June 2024 | | 54.56 | 54.2 | 0.75 | 0.0 | 0.0 | 54.95 | 0.0 | 0.0 | 0.0 | 54.95 | + | 6 | 30 | 01 July 2024 | | 0.0 | 54.56 | 0.44 | 0.0 | 0.0 | 55.0 | 0.0 | 0.0 | 0.0 | 55.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 200.0 | 3.23 | 0.0 | 0.0 | 203.23 | 17.01 | 0.0 | 0.0 | 186.22 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 183.57 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | +# --- close the loan --- # + When Admin sets the business date to "01 May 2024" + When Admin runs inline COB job for Loan + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 183.57 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 16 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 18 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 19 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 20 April 2024 | Accrual | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | + | 21 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 23 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 24 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 25 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 26 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 27 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 28 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 29 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 30 April 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + When Loan Pay-off is made on "01 May 2024" + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 183.57 | false | false | + | 01 May 2024 | Repayment | 185.34 | 183.57 | 1.77 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 May 2024 | Accrual | 0.28 | 0.0 | 0.28 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4635 @AdvancedPaymentAllocation + Scenario: Verify Loan re-amortization trn with CI before re-amortization - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S8 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_APPROVED_OVER_APPLIED_CAPITALIZED_INCOME | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- capitalized income transaction --- # + When Admin sets the business date to "01 March 2024" + When Admin runs inline COB job for Loan + And Admin adds capitalized income with "AUTOPAY" payment type to the loan on "01 March 2024" with "50" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | | | 01 March 2024 | | 50.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 31 | 01 April 2024 | | 88.04 | 29.01 | 0.68 | 0.0 | 0.0 | 29.69 | 0.0 | 0.0 | 0.0 | 29.69 | + | 4 | 30 | 01 May 2024 | | 58.86 | 29.18 | 0.51 | 0.0 | 0.0 | 29.69 | 0.0 | 0.0 | 0.0 | 29.69 | + | 5 | 31 | 01 June 2024 | | 29.51 | 29.35 | 0.34 | 0.0 | 0.0 | 29.69 | 0.0 | 0.0 | 0.0 | 29.69 | + | 6 | 30 | 01 July 2024 | | 0.0 | 29.51 | 0.17 | 0.0 | 0.0 | 29.68 | 0.0 | 0.0 | 0.0 | 29.68 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 150.0 | 2.77 | 0.0 | 0.0 | 152.77 | 17.01 | 0.0 | 0.0 | 135.76 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 29 February 2024 | Accrual | 1.05 | 0.0 | 1.05 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Capitalized Income | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 133.57 | false | false | +# --- re-amortization transaction --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | | | 01 March 2024 | | 50.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 3 | 31 | 01 April 2024 | | 100.47 | 33.1 | 0.9 | 0.0 | 0.0 | 34.0 | 0.0 | 0.0 | 0.0 | 34.0 | + | 4 | 30 | 01 May 2024 | | 67.18 | 33.29 | 0.71 | 0.0 | 0.0 | 34.0 | 0.0 | 0.0 | 0.0 | 34.0 | + | 5 | 31 | 01 June 2024 | | 33.69 | 33.49 | 0.51 | 0.0 | 0.0 | 34.0 | 0.0 | 0.0 | 0.0 | 34.0 | + | 6 | 30 | 01 July 2024 | | 0.0 | 33.69 | 0.32 | 0.0 | 0.0 | 34.01 | 0.0 | 0.0 | 0.0 | 34.01 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 150.0 | 3.02 | 0.0 | 0.0 | 153.02 | 17.01 | 0.0 | 0.0 | 136.01 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 March 2024 | Capitalized Income | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 133.57 | false | false | + | 01 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 29 February 2024 | Accrual | 1.05 | 0.0 | 1.05 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | +# --- undo capitalized income transaction --- # + When Admin sets the business date to "01 April 2024" + When Admin runs inline COB job for Loan + When Customer undo "1"th "Capitalized Income" transaction made on "01 March 2024" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 March 2024 | Capitalized Income | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 133.57 | true | false | + | 01 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Capitalized Income Amortization | 0.4 | 0.0 | 0.4 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.04 | 0.0 | 0.04 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | +# --- close the loan --- # + When Loan Pay-off is made on "01 April 2024" + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 March 2024 | Capitalized Income | 50.0 | 50.0 | 0.0 | 0.0 | 0.0 | 133.57 | true | false | + | 01 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Capitalized Income Amortization | 0.41 | 0.0 | 0.41 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Capitalized Income Amortization | 0.4 | 0.0 | 0.4 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Repayment | 84.54 | 83.57 | 0.97 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Capitalized Income Amortization Adjustment | 12.7 | 0.0 | 12.7 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 April 2024 | Accrual | 0.1 | 0.0 | 0.1 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4636 + Scenario: Verify Re-amortization with backdated interest pause after re-amortization, overlapping re-amortization partially - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S9 + When Admin sets the business date to "01 January 2024" + And Admin creates a client with random data + And Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- Check before re-amortization --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.47 | 16.58 | 0.43 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.75 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.94 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.94 | 0.1 | 0.0 | 0.0 | 17.04 | 0.0 | 0.0 | 0.0 | 17.04 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.09 | 0.0 | 0.0 | 102.09 | 17.01 | 0.0 | 0.0 | 85.08 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual Activity | 0.49 | 0.0 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | +# --- re-amortization transaction --- # + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + # --- interest pause --- # + When Admin sets the business date to "01 July 2024" + When Admin runs inline COB job for Loan + And Create an interest pause period with start date "10 February 2024" and end date "10 June 2024" + Then Loan term variations has 1 variation, with the following data: + | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | + | 11 | loanTermType.interestPause | interestPause | 10 February 2024 | 0.0 | 10 June 2024 | false | | + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.66 | 20.91 | 0.03 | 0.0 | 0.0 | 20.94 | 0.0 | 0.0 | 0.0 | 20.94 | + | 4 | 30 | 01 May 2024 | | 41.75 | 20.91 | 0.03 | 0.0 | 0.0 | 20.94 | 0.0 | 0.0 | 0.0 | 20.94 | + | 5 | 31 | 01 June 2024 | | 20.84 | 20.91 | 0.03 | 0.0 | 0.0 | 20.94 | 0.0 | 0.0 | 0.0 | 20.94 | + | 6 | 30 | 01 July 2024 | | 0.0 | 20.84 | 0.37 | 0.0 | 0.0 | 21.21 | 0.0 | 0.0 | 0.0 | 21.21 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.04 | 0.0 | 0.0 | 101.04 | 17.01 | 0.0 | 0.0 | 84.03 | + And Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.88 | 0.13 | 0.0 | 0.0 | 0.0 | false | true | + | 01 April 2024 | Accrual Activity | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | true | + | 01 May 2024 | Accrual Activity | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | true | + | 01 June 2024 | Accrual Activity | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | true | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 16 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 21 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 26 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 28 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 29 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 30 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 09 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 11 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 12 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 14 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 15 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 May 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 May 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 May 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 16 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 21 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 22 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 26 June 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 28 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 29 June 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 30 June 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | +# --- close the loan --- # + When Loan Pay-off is made on "01 July 2024" + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4637 + Scenario: Verify Re-amortization with reschedule with extra terms - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S10 + When Admin sets the business date to "01 January 2024" + And Admin creates a client with random data + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- First installment paid --- # + When Admin sets the business date to "01 February 2024" + When Admin runs inline COB job for Loan + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- reschedule with extra terms --- # + When Admin sets the business date to "02 February 2024" + When Admin runs inline COB job for Loan + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 March 2024 | 02 February 2024 | | | | 2 | | + Then Loan Repayment schedule has 8 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 71.84 | 11.73 | 0.49 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 3 | 31 | 01 April 2024 | | 60.04 | 11.8 | 0.42 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 4 | 30 | 01 May 2024 | | 48.17 | 11.87 | 0.35 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 5 | 31 | 01 June 2024 | | 36.23 | 11.94 | 0.28 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 6 | 30 | 01 July 2024 | | 24.22 | 12.01 | 0.21 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 7 | 31 | 01 August 2024 | | 12.14 | 12.08 | 0.14 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 8 | 31 | 01 September 2024 | | 0.0 | 12.14 | 0.07 | 0.0 | 0.0 | 12.21 | 0.0 | 0.0 | 0.0 | 12.21 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.54 | 0.0 | 0.0 | 102.54 | 17.01 | 0.0 | 0.0 | 85.53 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | +# --- check before re-amortization --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + Then Loan Repayment schedule has 8 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 71.84 | 11.73 | 0.49 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 3 | 31 | 01 April 2024 | | 60.07 | 11.77 | 0.45 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 4 | 30 | 01 May 2024 | | 48.2 | 11.87 | 0.35 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 5 | 31 | 01 June 2024 | | 36.26 | 11.94 | 0.28 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 6 | 30 | 01 July 2024 | | 24.25 | 12.01 | 0.21 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 7 | 31 | 01 August 2024 | | 12.17 | 12.08 | 0.14 | 0.0 | 0.0 | 12.22 | 0.0 | 0.0 | 0.0 | 12.22 | + | 8 | 31 | 01 September 2024 | | 0.0 | 12.17 | 0.07 | 0.0 | 0.0 | 12.24 | 0.0 | 0.0 | 0.0 | 12.24 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.57 | 0.0 | 0.0 | 102.57 | 17.01 | 0.0 | 0.0 | 85.56 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 31 January 2024 | Accrual | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 10 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 17 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 23 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 24 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 25 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 26 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 February 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 04 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 06 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 08 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | +# --- re-amortization transaction --- # + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 8 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 69.85 | 13.72 | 0.57 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 4 | 30 | 01 May 2024 | | 56.05 | 13.8 | 0.49 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 5 | 31 | 01 June 2024 | | 42.17 | 13.88 | 0.41 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 6 | 30 | 01 July 2024 | | 28.21 | 13.96 | 0.33 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 7 | 31 | 01 August 2024 | | 14.16 | 14.05 | 0.24 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 8 | 31 | 01 September 2024 | | 0.0 | 14.16 | 0.16 | 0.0 | 0.0 | 14.32 | 0.0 | 0.0 | 0.0 | 14.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.78 | 0.0 | 0.0 | 102.78 | 17.01 | 0.0 | 0.0 | 85.77 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 12.22 | 11.73 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has no new accrual data +# --- Reschedule after re-amortize --- # + When Admin sets the business date to "02 April 2024" + When Admin runs inline COB job for Loan + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 02 April 2024 | | | | 2 | | + Then Loan Repayment schedule has 10 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 69.85 | 13.72 | 0.57 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 4 | 30 | 01 May 2024 | | 56.05 | 13.8 | 0.49 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 5 | 31 | 01 June 2024 | | 42.17 | 13.88 | 0.41 | 0.0 | 0.0 | 14.29 | 0.0 | 0.0 | 0.0 | 14.29 | + | 6 | 30 | 01 July 2024 | | 33.87 | 8.3 | 0.33 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 7 | 31 | 01 August 2024 | | 25.52 | 8.35 | 0.28 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 8 | 31 | 01 September 2024 | | 17.12 | 8.4 | 0.23 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 9 | 30 | 01 October 2024 | | 8.59 | 8.53 | 0.1 | 0.0 | 0.0 | 8.63 | 0.0 | 0.0 | 0.0 | 8.63 | + | 10 | 31 | 01 November 2024 | | 0.0 | 8.59 | 0.05 | 0.0 | 0.0 | 8.64 | 0.0 | 0.0 | 0.0 | 8.64 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 3.04 | 0.0 | 0.0 | 103.04 | 17.01 | 0.0 | 0.0 | 86.03 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 12.22 | 11.73 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 15 March 2024 | Accrual Adjustment | 0.43 | 0.0 | 0.43 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | +# --- close loan --- # + When Loan Pay-off is made on "02 April 2024" + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 12.22 | 11.73 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Repayment | 84.56 | 83.57 | 0.99 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 02 April 2024 | Accrual | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4638 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging trn with Charge-off with interest zero after re-amortization - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S11 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | +# --- repayment to pay 1st instalment --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | +# --- re-amortization transaction --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | +# --- charge-off the loan --- # + When Admin sets the business date to "15 April 2024" + When Admin runs inline COB job for Loan + And Admin does charge-off the loan on "15 April 2024" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 41.89 | 20.97 | 0.35 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 20.57 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 20.57 | 0.0 | 0.0 | 0.0 | 20.57 | 0.0 | 0.0 | 0.0 | 20.57 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.54 | 0.0 | 0.0 | 101.54 | 17.01 | 0.0 | 0.0 | 84.53 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Charge-off | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Accrual | 0.09 | 0.0 | 0.09 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates re-amortization trn on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" is forbidden as loan was charged-off +# --- close the loan --- # + When Admin sets the business date to "01 May 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Charge-off | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has no new accrual data + When Loan Pay-off is made on "01 May 2024" + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Charge-off | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Repayment | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has no new accrual data + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4639 @AdvancedPaymentAllocation + Scenario: Verify Loan re-aging trn with accelerate maturity charge-off after re-amortization - interest bearing multidisb loan with equal amortization + interest split: Accrual and Accrual Activity S12 + When Admin sets the business date to "01 January 2024" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | +# --- repayment to pay 1st instalment --- # + When Admin sets the business date to "01 February 2024" + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | +# --- re-amortization transaction --- # + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + And Admin creates a Loan re-amortization transaction on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" + Then Loan Repayment schedule has 6 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 30 | 01 May 2024 | | 42.03 | 20.83 | 0.49 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 5 | 31 | 01 June 2024 | | 21.08 | 20.95 | 0.37 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 6 | 30 | 01 July 2024 | | 0.0 | 21.08 | 0.24 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 2.29 | 0.0 | 0.0 | 102.29 | 17.01 | 0.0 | 0.0 | 85.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | +# --- charge-off the loan --- # + When Admin sets the business date to "15 April 2024" + When Admin runs inline COB job for Loan + And Admin does charge-off the loan on "15 April 2024" + Then Loan Repayment schedule has 4 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | | 62.86 | 20.71 | 0.61 | 0.0 | 0.0 | 21.32 | 0.0 | 0.0 | 0.0 | 21.32 | + | 4 | 14 | 15 April 2024 | | 0.0 | 62.86 | 0.35 | 0.0 | 0.0 | 63.21 | 0.0 | 0.0 | 0.0 | 63.21 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100.0 | 1.54 | 0.0 | 0.0 | 101.54 | 17.01 | 0.0 | 0.0 | 84.53 | + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Charge-off | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has the following new accrual data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 14 March 2024 | Accrual | 1.27 | 0.0 | 1.27 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual Adjustment | 0.42 | 0.0 | 0.42 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 April 2024 | Accrual | 0.03 | 0.0 | 0.03 | 0.0 | 0.0 | 0.0 | false | false | + | 11 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 12 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Accrual | 0.09 | 0.0 | 0.09 | 0.0 | 0.0 | 0.0 | false | false | + And Admin creates re-amortization trn on current business date with reAmortizationInterestHandling "EQUAL_AMORTIZATION_INTEREST_SPLIT" is forbidden as loan was charged-off +# --- close the loan --- # + When Loan Pay-off is made on "15 April 2024" + Then Loan Transactions tab has the following data without accruals: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | + | 15 March 2024 | Re-amortize | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Charge-off | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + | 15 April 2024 | Repayment | 84.53 | 83.57 | 0.96 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan Transactions tab has no new accrual data + Then Loan is closed with zero outstanding balance and it's all installments have obligations met diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature index 53e9ffee7e1..bd1f4383765 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -6751,3 +6751,177 @@ Feature: LoanRepayment Then Customer undo "1"th transaction made on "01 February 2024" results a 403 error and "update not allowed as loan transaction is linked to other transactions" error message When Loan Pay-off is made on "15 March 2024" Then Loan is closed with zero outstanding balance and it's all installments have obligations met + + @TestRailId:C4683 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, prepayment with NEXT_INSTALLMENT strategy + When Admin sets the business date to "23 February 2026" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC | 01 January 2026 | 25000000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12 | MONTHS | 1 | MONTHS | 12 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "25000000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "25000000" EUR transaction amount + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 2 | 28 | 01 March 2026 | | 21039772.25 | 1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 3 | 31 | 01 April 2026 | | 19033564.29 | 2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 4 | 30 | 01 May 2026 | | 17000651.89 | 2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 5 | 31 | 01 June 2026 | | 14953278.1 | 2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 6 | 30 | 01 July 2026 | | 12880121.78 | 2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 7 | 31 | 01 August 2026 | | 10790752.45 | 2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 8 | 31 | 01 September 2026 | | 8680088.72 | 2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 9 | 30 | 01 October 2026 | | 6545059.84 | 2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 10 | 31 | 01 November 2026 | | 4391124.95 | 2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 11 | 30 | 01 December 2026 | | 2213793.97 | 2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 12 | 31 | 01 January 2027 | | 0.0 | 2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0 | 0.0 | 0.0 | 2236356.47 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 | 0.0 | 0.0 | 0.0 | 26663404.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + When Loan Pay-off is made on "23 February 2026" + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 0.0 | 2220640.71 | 0.0 | + | 2 | 28 | 01 March 2026 | 23 February 2026 | 20813513.1 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2026 | 23 February 2026 | 18592872.39 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2026 | 23 February 2026 | 16372231.68 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2026 | 23 February 2026 | 14151590.97 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2026 | 23 February 2026 | 11930950.26 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 7 | 31 | 01 August 2026 | 23 February 2026 | 9710309.55 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 8 | 31 | 01 September 2026 | 23 February 2026 | 7489668.84 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 9 | 30 | 01 October 2026 | 23 February 2026 | 5269028.13 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 10 | 31 | 01 November 2026 | 23 February 2026 | 3048387.42 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 11 | 30 | 01 December 2026 | 23 February 2026 | 827746.71 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 | 827746.71 | 180821.92 | 0.0 | 0.0 | 1008568.63 | 1008568.63 | 1008568.63 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 | 25435616.44 | 23214975.73 | 2220640.71 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + | 23 February 2026 | Accrual | 435616.44 | 0.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C4684 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, prepayment with LAST_INSTALLMENT strategy + When Admin sets the business date to "23 February 2026" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC | 01 January 2026 | 25000000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12 | MONTHS | 1 | MONTHS | 12 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "25000000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "25000000" EUR transaction amount + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 2 | 28 | 01 March 2026 | | 21039772.25 | 1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 3 | 31 | 01 April 2026 | | 19033564.29 | 2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 4 | 30 | 01 May 2026 | | 17000651.89 | 2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 5 | 31 | 01 June 2026 | | 14953278.1 | 2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 6 | 30 | 01 July 2026 | | 12880121.78 | 2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 7 | 31 | 01 August 2026 | | 10790752.45 | 2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 8 | 31 | 01 September 2026 | | 8680088.72 | 2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 9 | 30 | 01 October 2026 | | 6545059.84 | 2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 10 | 31 | 01 November 2026 | | 4391124.95 | 2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 11 | 30 | 01 December 2026 | | 2213793.97 | 2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 12 | 31 | 01 January 2027 | | 0.0 | 2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0 | 0.0 | 0.0 | 2236356.47 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 | 0.0 | 0.0 | 0.0 | 26663404.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + When Loan Pay-off is made on "23 February 2026" + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 0.0 | 2220640.71 | 0.0 | + | 2 | 28 | 01 March 2026 | 23 February 2026 | 22206407.14 | 827746.67 | 180821.92 | 0.0 | 0.0 | 1008568.59 | 1008568.59 | 1008568.59 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2026 | 23 February 2026 | 19985766.43 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2026 | 23 February 2026 | 17765125.72 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2026 | 23 February 2026 | 15544485.01 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2026 | 23 February 2026 | 13323844.3 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 7 | 31 | 01 August 2026 | 23 February 2026 | 11103203.59 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 8 | 31 | 01 September 2026 | 23 February 2026 | 8882562.88 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 9 | 30 | 01 October 2026 | 23 February 2026 | 6661922.17 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 10 | 31 | 01 November 2026 | 23 February 2026 | 4441281.46 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 11 | 30 | 01 December 2026 | 23 February 2026 | 2220640.75 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 | 2220640.75 | 0.0 | 0.0 | 0.0 | 2220640.75 | 2220640.75 | 2220640.75 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 | 25435616.44 | 23214975.73 | 2220640.71 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + | 23 February 2026 | Accrual | 435616.44 | 0.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C4685 @AdvancedPaymentAllocation @ProgressiveLoanSchedule + Scenario: Verify AdvancedPaymentAllocation behaviour: loanScheduleProcessingType-vertical, prepayment with NEXT_LAST_INSTALLMENT strategy + When Admin sets the business date to "23 February 2026" + When Admin creates a client with random data + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "NEXT_LAST_INSTALLMENT" future installment allocation rule + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC | 01 January 2026 | 25000000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12 | MONTHS | 1 | MONTHS | 12 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2026" with "25000000" amount and expected disbursement date on "01 January 2026" + When Admin successfully disburse the loan on "01 January 2026" with "25000000" EUR transaction amount + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 2 | 28 | 01 March 2026 | | 21039772.25 | 1994381.56 | 226259.15 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 3 | 31 | 01 April 2026 | | 19033564.29 | 2006207.96 | 214432.75 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 4 | 30 | 01 May 2026 | | 17000651.89 | 2032912.4 | 187728.31 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 5 | 31 | 01 June 2026 | | 14953278.1 | 2047373.79 | 173266.92 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 6 | 30 | 01 July 2026 | | 12880121.78 | 2073156.32 | 147484.39 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 7 | 31 | 01 August 2026 | | 10790752.45 | 2089369.33 | 131271.38 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 8 | 31 | 01 September 2026 | | 8680088.72 | 2110663.73 | 109976.98 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 9 | 30 | 01 October 2026 | | 6545059.84 | 2135028.88 | 85611.83 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 10 | 31 | 01 November 2026 | | 4391124.95 | 2153934.89 | 66705.82 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 11 | 30 | 01 December 2026 | | 2213793.97 | 2177330.98 | 43309.73 | 0.0 | 0.0 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | + | 12 | 31 | 01 January 2027 | | 0.0 | 2213793.97 | 22562.5 | 0.0 | 0.0 | 2236356.47 | 0.0 | 0.0 | 0.0 | 2236356.47 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 1663404.28 | 0.0 | 0.0 | 26663404.28 | 0.0 | 0.0 | 0.0 | 26663404.28 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + When Loan Pay-off is made on "23 February 2026" + Then Loan Repayment schedule has 12 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2026 | | 25000000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2026 | 23 February 2026 | 23034153.81 | 1965846.19 | 254794.52 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 0.0 | 2220640.71 | 0.0 | + | 2 | 28 | 01 March 2026 | 23 February 2026 | 20813513.1 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2026 | 23 February 2026 | 19985766.43 | 827746.67 | 180821.92 | 0.0 | 0.0 | 1008568.59 | 1008568.59 | 1008568.59 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2026 | 23 February 2026 | 17765125.72 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 5 | 31 | 01 June 2026 | 23 February 2026 | 15544485.01 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 6 | 30 | 01 July 2026 | 23 February 2026 | 13323844.3 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 7 | 31 | 01 August 2026 | 23 February 2026 | 11103203.59 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 8 | 31 | 01 September 2026 | 23 February 2026 | 8882562.88 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 9 | 30 | 01 October 2026 | 23 February 2026 | 6661922.17 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 10 | 31 | 01 November 2026 | 23 February 2026 | 4441281.46 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 11 | 30 | 01 December 2026 | 23 February 2026 | 2220640.75 | 2220640.71 | 0.0 | 0.0 | 0.0 | 2220640.71 | 2220640.71 | 2220640.71 | 0.0 | 0.0 | + | 12 | 31 | 01 January 2027 | 23 February 2026 | 0.0 | 2220640.75 | 0.0 | 0.0 | 0.0 | 2220640.75 | 2220640.75 | 2220640.75 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 25000000.0 | 435616.44 | 0.0 | 0.0 | 25435616.44 | 25435616.44 | 23214975.73 | 2220640.71 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2026 | Disbursement | 25000000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 25000000.0 | + | 23 February 2026 | Repayment | 25435616.44 | 25000000.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + | 23 February 2026 | Accrual | 435616.44 | 0.0 | 435616.44 | 0.0 | 0.0 | 0.0 | + Then Loan is closed with zero outstanding balance and it's all installments have obligations met + When Admin set "LP2_DOWNPAYMENT_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_VERTICAL_INTEREST_RECALC" loan product "REPAYMENT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Reporting.feature b/fineract-e2e-tests-runner/src/test/resources/features/Reporting.feature new file mode 100644 index 00000000000..e01d355c76e --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/Reporting.feature @@ -0,0 +1,279 @@ +@ReportingFeature +Feature: Reporting + + @TestRailId:C4686 + Scenario: Verify Transaction Summary Reports contain all buydown fee transaction types + When Admin sets the business date to "01 January 2024" + And Admin creates a new office + And Admin creates a client with random data in the last created office + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + And Admin adds buy down fee with "AUTOPAY" payment type to the loan on "01 January 2024" with "50" EUR transaction amount + And Admin sets the business date to "31 January 2024" + And Admin runs inline COB job for Loan + And Admin sets the business date to "01 February 2024" + And Admin runs inline COB job for Loan + And Admin adds buy down fee adjustment with "AUTOPAY" payment type to the loan on "01 February 2024" with "10" EUR transaction amount + And Admin sets the business date to "02 February 2024" + And Admin adds buy down fee adjustment of buy down fee transaction made on "01 January 2024" with "AUTOPAY" payment type to the loan on "10 January 2024" with "25" EUR transaction amount + And Admin runs inline COB job for Loan +# --- Transaction Summary Report --- + Then Transaction Summary Report for date "01 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Fees | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Interest | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Penalty | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Principal | | 100.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "31 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.55 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "01 February 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -10.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Fees | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Interest | | -6.63 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Penalty | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Principal | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Unallocated Credit (UNC) | | 0.0 | +# --- Transaction Summary Report with Asset Owner --- + And Transaction Summary Report with Asset Owner for date "01 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Fees | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Interest | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Penalty | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Principal | | 100.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + And Transaction Summary Report with Asset Owner for date "31 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.55 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + And Transaction Summary Report with Asset Owner for date "01 February 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -10.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Fees | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Interest | | -6.63 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Penalty | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Principal | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization Adjustment | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + + @TestRailId:C4687 + Scenario: Verify Transaction Summary Reports with buyDownFeeIncomeType = FEE + When Admin sets the business date to "01 January 2024" + And Admin creates a new office + And Admin creates a client with random data in the last created office + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024" + And Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount + And Admin adds buy down fee with "AUTOPAY" payment type to the loan on "01 January 2024" with "50" EUR transaction amount + And Admin sets the business date to "31 January 2024" + And Admin runs inline COB job for Loan + And Admin sets the business date to "01 February 2024" + And Admin runs inline COB job for Loan + And Admin adds buy down fee adjustment with "AUTOPAY" payment type to the loan on "01 February 2024" with "10" EUR transaction amount + And Admin sets the business date to "02 February 2024" + And Admin adds buy down fee adjustment of buy down fee transaction made on "01 January 2024" with "AUTOPAY" payment type to the loan on "10 January 2024" with "25" EUR transaction amount + And Admin runs inline COB job for Loan +# --- Transaction Summary Report --- + Then Transaction Summary Report for date "01 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Fees | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Interest | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Penalty | | 0.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Principal | | 100.0 | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "31 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Apply Charges | | | 0 | Interest | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Fees | | 0.55 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Interest | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "01 February 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Apply Charges | | | 0 | Interest | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -10.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Fees | | -6.63 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Interest | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Penalty | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Principal | | 0.0 | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Unallocated Credit (UNC) | | 0.0 | +# --- Transaction Summary Report with Asset Owner --- + And Transaction Summary Report with Asset Owner for date "01 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Fees | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Interest | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Penalty | | 0.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Principal | | 100.0 | | | + | 2024-01-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Disbursement | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + And Transaction Summary Report with Asset Owner for date "31 January 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Apply Charges | | | 0 | Interest | | | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Fees | | 0.55 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Interest | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | | | + | 2024-01-31 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + And Transaction Summary Report with Asset Owner for date "01 February 2024" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | Asset_owner_id | From_asset_owner_id | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Apply Charges | | | 0 | Interest | | | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -10.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Fees | | -6.63 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Interest | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Penalty | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Principal | | 0.0 | | | + | 2024-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES_FEE_INCOME | Buy Down Fee Amortization Adjustment | | | 0 | Unallocated Credit (UNC) | | 0.0 | | | + + @TestRailId:C4688 + Scenario: Verify Transaction Summary Reports with Buydown fee - happy path + When Admin sets the business date to "01 February 2026" + And Admin creates a new office + And Admin creates a client with random data in the last created office + And Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | 01 February 2026 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 February 2026" with "100" amount and expected disbursement date on "01 February 2026" + And Admin successfully disburse the loan on "01 February 2026" with "100" EUR transaction amount + And Admin adds buy down fee with "AUTOPAY" payment type to the loan on "01 February 2026" with "50" EUR transaction amount + And Admin sets the business date to "02 February 2026" + And Admin runs inline COB job for Loan + And Admin sets the business date to "03 February 2026" + And Admin runs inline COB job for Loan + And Admin adds buy down fee adjustment with "AUTOPAY" payment type to the loan on "03 February 2026" with "25" EUR transaction amount + And Admin sets the business date to "04 February 2026" + And Admin runs inline COB job for Loan + And Admin sets the business date to "05 February 2026" + And Admin runs inline COB job for Loan +# --- Transaction Summary Report with Asset Owner --- + Then Transaction Summary Report with Asset Owner for date "01 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.28 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Principal | | 100.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report with Asset Owner for date "02 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | 0.02 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.27 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report with Asset Owner for date "03 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | 0.02 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -25.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.14 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | +# --- Transaction Summary Report --- + And Transaction Summary Report for date "01 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Principal | | 50.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.28 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Principal | | 100.0 | + | 2026-02-01 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Disbursement | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "02 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | 0.02 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.27 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-02 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + And Transaction Summary Report for date "03 February 2026" has the following data: + | TransactionDate | Product | TransactionType_Name | PaymentType_Name | chargetype | Reversed | Allocation_Type | Chargeoff_ReasonCode | Transaction_Amount | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Apply Charges | | | 0 | Interest | | 0.02 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Fees | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Interest | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Penalty | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Principal | | -25.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Adjustment | AUTOPAY | | 0 | Unallocated Credit (UNC) | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Fees | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Interest | | 0.14 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Penalty | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Principal | | 0.0 | + | 2026-02-03 | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | Buy Down Fee Amortization | | | 0 | Unallocated Credit (UNC) | | 0.0 | + + diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature new file mode 100644 index 00000000000..63c2acbc5cd --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalLoanProduct.feature @@ -0,0 +1,135 @@ +@WorkingCapitalLoanProductFeature +Feature: WorkingCapitalLoanProduct + + @TestRailId:C70208 + Scenario: Verify Working capital Loan Product create/edit/delete with valid data - happy path - UC1 + When Admin creates a new Working Capital Loan Product + When Admin updates a Working Capital Loan Product + Then Admin deletes a Working Capital Loan Product + Then Admin checks a Working Capital Loan Product is deleted and doesn't exist + + @TestRailId:C70209 + Scenario: Verify Working capital Loan Product create/edit/delete via external-id with valid data - happy path - UC2 + When Admin creates a new Working Capital Loan Product with external-id + When Admin updates a Working Capital Loan Product via external-id + Then Admin deletes a Working Capital Loan Product via external-id + Then Admin checks a Working Capital Loan Product is deleted and doesn't exist via external-id + + @TestRailId:C70210 + Scenario Outline: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with mandatory fields - UC3 + Then Admin failed to create a new Working Capital Loan Product field "" with empty or null mandatory data + + Examples: + | wcp_field_name_mandatory | wcp_empty_field_value_mandatory | + | name | "null" | + | name | "" | + | shortName | "" | + | shortName | "null" | + | currencyCode | "" | + | currencyCode | "null" | + | digitsAfterDecimal | "null" | + | amortizationType | "null" | + | npvDayCount | "null" | + | principal | "null" | + | periodPaymentRate | "null" | + | repaymentFrequencyType | "null" | + + @TestRailId:C70211 + Scenario Outline: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with max allowed length - UC4 + Then Admin failed to create a new Working Capital Loan Product field "" with max length data while max allowed is + + Examples: + | wcp_field_name_max_length | wcp_invalid_field_value_max_length | wcp_invalid_field_value_max_allowed_length | + | name | 101 | 100 | + | shortName | 9 | 4 | + | description | 550 | 500 | + | currencyCode | 4 | 3 | + + @TestRailId:C70212 + Scenario Outline: Verify Working capital Loan Product update with invalid data shall outcome with error - validation check with max allowed length - UC5 + Then Admin failed to update a new Working Capital Loan Product field "" with max length data while max allowed is + + Examples: + | wcp_field_name_max_length | wcp_invalid_field_value_max_length | wcp_invalid_field_value_max_allowed_length | + | name | 111 | 100 | + | shortName | 5 | 4 | + | description | 600 | 500 | + | currencyCode | 10 | 3 | + + @TestRailId:C70213 + Scenario Outline: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with zero values - UC6 + Then Admin failed to create a new Working Capital Loan Product field "" with zero incorrect value + + Examples: + | wcp_field_name_zero_value | + | npvDayCount | + | principal | + | minPrincipal | + | maxPrincipal | + | delinquencyBucketId | + + @TestRailId:C70214 + Scenario Outline: Verify Working capital Loan Product update with invalid data shall outcome with error - validation check with zero values - UC7 + Then Admin failed to update a new Working Capital Loan Product field "" with zero incorrect value + + Examples: + | wcp_field_name_zero_value | + | npvDayCount | + | principal | + | minPrincipal | + | maxPrincipal | + | delinquencyBucketId | + + @TestRailId:C70215 + Scenario Outline: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with diff values - U8 + Then Admin failed to create a new Working Capital Loan Product field "" with invalid data and got an error + + Examples: + | wcp_field_name | wcp_invalid_field_value | wcp_error_message | + | digitsAfterDecimal | "25" | "The parameter `digitsAfterDecimal` must be between 0 and 6." | + | inMultiplesOf | "-1" | "The parameter `inMultiplesOf` must be zero or greater." | + | periodPaymentRate | "-1" | "The parameter `periodPaymentRate` must be greater than or equal to 0." | + | locale | "null" | "The parameter `digitsAfterDecimal` requires a `locale` parameter to be passed with it." | + | locale | "" | "The parameter `locale` is invalid. It cannot be blank." | + + @TestRailId:C70216 + Scenario Outline: Verify Working capital Loan Product update with invalid data shall outcome with error - validation check with diff values - UC9 + Then Admin failed to update a new Working Capital Loan Product field "" with invalid data and got an error + + Examples: + | wcp_field_name | wcp_invalid_field_value | wcp_error_message | + | digitsAfterDecimal | "25" | "The parameter `digitsAfterDecimal` must be between 0 and 6." | + | inMultiplesOf | "-1" | "The parameter `inMultiplesOf` must be zero or greater." | + | periodPaymentRate | "-1" | "The parameter `periodPaymentRate` must be greater than or equal to 0." | + + @TestRailId:C70217 + Scenario: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with number of payment allocation rules - UC10 + Then Admin failed to create a new Working Capital Loan Product with invalid number of payment allocation rules + + @TestRailId:C70218 + Scenario: Verify Working capital Loan Product create with invalid data shall outcome with error - validation check with payment allocation rules - UC11 + Then Admin failed to create a new Working Capital Loan Product with invalid value of payment allocation rules + + @TestRailId:C70219 + Scenario: Verify Working capital Loan Product update with invalid data shall outcome with error - validation check with number of payment allocation rules - UC12 + Then Admin failed to update a new Working Capital Loan Product with invalid number of payment allocation rules + + @TestRailId:C70220 + Scenario: Verify Working capital Loan Product update with invalid data shall outcome with error - validation check with payment allocation rules - UC13 + Then Admin failed to update a new Working Capital Loan Product with invalid value of payment allocation rules + + @TestRailId:C70221 + Scenario Outline: Verify Working capital Loan Product delete with invalid data shall outcome with error - validation check with id - UC14 + Then Admin failed to delete a Working Capital Loan Product with id that doesn't exist + Examples: + | wcp_field_name_incorrect_value | + | 103284 | + | 0 | + + @TestRailId:C70222 + Scenario Outline: Verify Working capital Loan Product retrieve with invalid data shall outcome with error - validation check with id - UC15 + Then Admin failed to retrieve a Working Capital Loan Product with id that doesn't exist + Examples: + | wcp_field_name_incorrect_value | + | 565465 | + | 0 | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapital_COB.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapital_COB.feature new file mode 100644 index 00000000000..d360f08a9f7 --- /dev/null +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapital_COB.feature @@ -0,0 +1,218 @@ +@WCCOBFeature @WC +Feature: Working Capital COB Job + + Background: + Given Global configuration "enable-business-date" is enabled + + @TestRailId:C4695 + Scenario: Verify WC COB job registration, default business step, and scheduler metadata + Then Admin checks that configured business jobs contain "WORKING_CAPITAL_LOAN_CLOSE_OF_BUSINESS" + Then Admin verifies configured business steps for "WORKING_CAPITAL_LOAN_CLOSE_OF_BUSINESS" match: + | stepName | order | + | DUMMY_BUSINESS_STEP | 1 | + Then Admin verifies scheduler job "WC_COB" has display name "Working Capital Loan COB" + Then Admin verifies scheduler job "WC_COB" has active status "false" + + @TestRailId:C4696 + Scenario: WC COB and Loan COB coexistence — both jobs listed and execute without interference + Then Admin checks that configured business jobs contain "LOAN_CLOSE_OF_BUSINESS" + Then Admin checks that configured business jobs contain "WORKING_CAPITAL_LOAN_CLOSE_OF_BUSINESS" + When Admin sets the business date to "01 January 2024" + When Admin runs COB job + When Admin runs WC COB job + + @TestRailId:C4697 + Scenario: WC COB executes on consecutive dates with Loan COB interleaved + When Admin sets the business date to "01 January 2024" + When Admin runs WC COB job + When Admin runs COB job + When Admin sets the business date to "02 January 2024" + When Admin runs WC COB job + + @TestRailId:C70320 + Scenario: WC COB updates lastClosedBusinessDate for a single active loan + # Behavioral test: inserts a WC loan via JDBC, runs COB, verifies lastClosedBusinessDate is set. + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + + @TestRailId:C70231 + Scenario: WC COB processes multiple loans in a single run + When Admin sets the business date to "01 January 2024" + Given Admin inserts 3 active WC loans into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + + @TestRailId:C70232 + Scenario: WC COB advances lastClosedBusinessDate over consecutive business dates + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + When Admin sets the business date to "02 January 2024" + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "01 January 2024" + Then Admin verifies all inserted WC loans have version 2 + + @TestRailId:C70233 + Scenario: WC COB does not reprocess loans already closed for the business date + # Verifies idempotency — running COB twice on the same business date doesn't cause errors or duplicate processing. + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 + + @TestRailId:C70234 + Scenario: WC COB skips loans with ineligible status (closed obligations met) + # COB only processes non-closed statuses: SUBMITTED_AND_PENDING_APPROVAL, APPROVED, ACTIVE, + # TRANSFER_IN_PROGRESS, TRANSFER_ON_HOLD. Closed loans should be skipped. + When Admin sets the business date to "01 January 2024" + Given Admin inserts a WC loan with status "CLOSED_OBLIGATIONS_MET" into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have null lastClosedBusinessDate + + @TestRailId:C70235 + Scenario: WC COB processes loans with eligible non-active statuses + When Admin sets the business date to "01 January 2024" + Given Admin inserts a WC loan with status "SUBMITTED_AND_PENDING_APPROVAL" into the database + Given Admin inserts a WC loan with status "APPROVED" into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + + @TestRailId:C70236 + Scenario: WC COB processes loans with transfer statuses + When Admin sets the business date to "01 January 2024" + Given Admin inserts a WC loan with status "TRANSFER_IN_PROGRESS" into the database + Given Admin inserts a WC loan with status "TRANSFER_ON_HOLD" into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + + @TestRailId:C70237 + Scenario: WC COB skips loans already closed for the current business date + When Admin sets the business date to "01 January 2024" + Given Admin inserts a WC loan with status "ACTIVE" and lastClosedBusinessDate "31 December 2023" into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 0 + + @TestRailId:C70238 + Scenario: WC COB advances a loan that is exactly one day behind + When Admin sets the business date to "02 January 2024" + Given Admin inserts a WC loan with status "ACTIVE" and lastClosedBusinessDate "31 December 2023" into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "01 January 2024" + + @TestRailId:C70239 + Scenario: WC COB releases all account locks after completion + # After COB completes, no lingering account locks should remain for processed loans. + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have no account locks + + @TestRailId:C70240 + Scenario: WC COB handles a batch of 10 loans + When Admin sets the business date to "01 January 2024" + Given Admin inserts 10 active WC loans into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + + @TestRailId:C70241 + Scenario: WC COB increments loan version after processing + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + Then Admin verifies all inserted WC loans have version 0 + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 + + @TestRailId:C70242 + Scenario: WC COB processes eligible loans and skips ineligible ones in the same batch + # Mix of eligible (ACTIVE) and ineligible (CLOSED_OBLIGATIONS_MET) loans — only eligible should be updated. + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + Given Admin inserts a WC loan with status "CLOSED_OBLIGATIONS_MET" into the database + When Admin runs WC COB job + Then Admin verifies inserted WC loan 1 has lastClosedBusinessDate "31 December 2023" + Then Admin verifies inserted WC loan 2 has null lastClosedBusinessDate + + #----------# + @TestRailId:C70243 + Scenario: Inline WC COB processes a single loan and releases locks + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have no account locks + Then Admin verifies all inserted WC loans have version 1 + + @TestRailId:C70244 + Scenario: Inline WC COB processes multiple loans in a single request + When Admin sets the business date to "01 January 2024" + Given Admin inserts 3 active WC loans into the database + When Admin runs inline COB job for all Working Capital Loans + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 + + @TestRailId:C70245 + Scenario: Inline WC COB and batch WC COB coexistence + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 + When Admin sets the business date to "02 January 2024" + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "01 January 2024" + Then Admin verifies all inserted WC loans have version 2 + + @TestRailId:C70246 + Scenario: Inline WC COB advances lastClosedBusinessDate over consecutive business dates + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + When Admin sets the business date to "02 January 2024" + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "01 January 2024" + Then Admin verifies all inserted WC loans have version 2 + + @TestRailId:C70247 + Scenario: Inline WC COB advances lastClosedBusinessDate over skipped business dates + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + When Admin sets the business date to "05 January 2024" + When Admin runs inline COB job for Working Capital Loan + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "04 January 2024" + Then Admin verifies all inserted WC loans have version 5 + + @TestRailId:C70248 + Scenario: Working Capital COB catch up advances lastClosedBusinessDate over skipped business dates + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + When Admin sets the business date to "05 January 2024" + When Admin runs Working Capital COB catch up + When Admin runs COB catch up + Then Admin checks that WC Loan COB is running until the current business date + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "04 January 2024" + Then Admin verifies all inserted WC loans have version 5 + + @TestRailId:C70249 + Scenario: WC COB catch-up is skipped when loans are already up to date + When Admin sets the business date to "01 January 2024" + Given Admin inserts an active WC loan into the database + When Admin runs WC COB job + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 + When Admin runs Working Capital COB catch up + Then Admin verifies all inserted WC loans have lastClosedBusinessDate "31 December 2023" + Then Admin verifies all inserted WC loans have version 1 \ No newline at end of file diff --git a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java index 783b3c26dbc..9f0a91564a0 100644 --- a/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java +++ b/fineract-investor/src/main/java/org/apache/fineract/investor/api/ExternalAssetOwnersApiResource.java @@ -131,6 +131,7 @@ public CommandProcessingResult transferRequestWithLoanExternalId(@PathParam("loa @Path("/transfers/{id}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Transfer external asset", operationId = "transferRequestWithId") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferResponse.class))) @ApiResponse(responseCode = "403", description = "Transfer cannot be initiated") public CommandProcessingResult transferRequestWithId(@PathParam("id") final Long id, @@ -145,6 +146,7 @@ public CommandProcessingResult transferRequestWithId(@PathParam("id") final Long @Path("/transfers/external-id/{externalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Transfer external asset by external ID", operationId = "transferRequestWithIdByExternalId") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ExternalAssetOwnersApiResourceSwagger.PostInitiateTransferResponse.class))) @ApiResponse(responseCode = "403", description = "Transfer cannot be initiated") public CommandProcessingResult transferRequestWithId(@PathParam("externalId") final String externalId, diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java index 76fa0fa416b..f060ca3d1bf 100644 --- a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/api/LoanOriginatorApiResource.java @@ -62,7 +62,7 @@ public class LoanOriginatorApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a new loan originator", description = "Creates a new loan originator record. Requires CREATE_LOAN_ORIGINATOR permission.") + @Operation(summary = "Create a new loan originator", operationId = "createLoanOriginator", description = "Creates a new loan originator record. Requires CREATE_LOAN_ORIGINATOR permission.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PostLoanOriginatorsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PostLoanOriginatorsResponse.class))) @ApiResponse(responseCode = "400", description = "Required parameter is missing or incorrect format") @@ -75,7 +75,7 @@ public CommandProcessingResult create(@Parameter(hidden = true) final String api @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List all loan originators", description = "Retrieves all loan originator records. Requires READ_LOAN_ORIGINATOR permission.") + @Operation(summary = "List all loan originators", operationId = "retrieveAllLoanOriginators", description = "Retrieves all loan originator records. Requires READ_LOAN_ORIGINATOR permission.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class)))) @ApiResponse(responseCode = "403", description = "Insufficient permissions") public List retrieveAll() { @@ -101,7 +101,7 @@ public LoanOriginatorTemplateData retrieveLoanOriginatorTemplate() { @Path("{originatorId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a loan originator by ID", description = "Retrieves a loan originator by its internal ID. Requires READ_LOAN_ORIGINATOR permission.") + @Operation(summary = "Retrieve a loan originator by ID", operationId = "retrieveOneLoanOriginator", description = "Retrieves a loan originator by its internal ID. Requires READ_LOAN_ORIGINATOR permission.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.GetLoanOriginatorsResponse.class))) @ApiResponse(responseCode = "403", description = "Insufficient permissions") @ApiResponse(responseCode = "404", description = "Originator not found") @@ -130,7 +130,7 @@ public LoanOriginatorData retrieveByExternalId( @Path("{originatorId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a loan originator by ID", description = "Updates a loan originator by its internal ID. Requires UPDATE_LOAN_ORIGINATOR permission.") + @Operation(summary = "Update a loan originator by ID", operationId = "updateLoanOriginator", description = "Updates a loan originator by its internal ID. Requires UPDATE_LOAN_ORIGINATOR permission.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.PutLoanOriginatorsResponse.class))) @ApiResponse(responseCode = "400", description = "Incorrect format") @@ -167,7 +167,7 @@ public CommandProcessingResult updateByExternalId( @Path("{originatorId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a loan originator by ID", description = "Deletes a loan originator by its internal ID. Requires DELETE_LOAN_ORIGINATOR permission.") + @Operation(summary = "Delete a loan originator by ID", operationId = "deleteLoanOriginator", description = "Deletes a loan originator by its internal ID. Requires DELETE_LOAN_ORIGINATOR permission.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanOriginatorApiResourceSwagger.DeleteLoanOriginatorsResponse.class))) @ApiResponse(responseCode = "403", description = "Originator is mapped to loans and cannot be deleted, or insufficient permissions") @ApiResponse(responseCode = "404", description = "Originator not found") diff --git a/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanChargeDataV1OriginatorEnricher.java b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanChargeDataV1OriginatorEnricher.java new file mode 100644 index 00000000000..df59ee046d5 --- /dev/null +++ b/fineract-loan-origination/src/main/java/org/apache/fineract/portfolio/loanorigination/enricher/LoanChargeDataV1OriginatorEnricher.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanorigination.enricher; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.avro.loan.v1.LoanChargeDataV1; +import org.apache.fineract.avro.loan.v1.OriginatorDetailsV1; +import org.apache.fineract.infrastructure.core.service.DataEnricher; +import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginator; +import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMapping; +import org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(value = "fineract.module.loan-origination.enabled", havingValue = "true") +public class LoanChargeDataV1OriginatorEnricher implements DataEnricher { + + private final LoanOriginatorMappingRepository loanOriginatorMappingRepository; + private final LoanOriginatorAvroMapper loanOriginatorAvroMapper; + + @Override + public boolean isDataTypeSupported(final Class dataType) { + return dataType.isAssignableFrom(LoanChargeDataV1.class); + } + + @Override + public void enrich(final LoanChargeDataV1 data) { + if (data == null || data.getLoanId() == null) { + return; + } + + final List mappings = loanOriginatorMappingRepository.findByLoanIdWithOriginatorDetails(data.getLoanId()); + if (mappings == null || mappings.isEmpty()) { + return; + } + + final List originators = new ArrayList<>(); + for (LoanOriginatorMapping mapping : mappings) { + final LoanOriginator originator = mapping.getOriginator(); + if (originator != null) { + final OriginatorDetailsV1 originatorDetails = loanOriginatorAvroMapper.toAvro(originator); + if (originatorDetails != null) { + originators.add(originatorDetails); + } + } + } + + if (!originators.isEmpty()) { + data.setOriginators(originators); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java b/fineract-loan/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java similarity index 100% rename from fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java rename to fineract-loan/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java diff --git a/fineract-loan/src/main/java/org/apache/fineract/cob/service/RetrieveLoanIdService.java b/fineract-loan/src/main/java/org/apache/fineract/cob/service/RetrieveLoanIdService.java new file mode 100644 index 00000000000..640368cbe11 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/cob/service/RetrieveLoanIdService.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +public interface RetrieveLoanIdService extends RetrieveIdService { + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 9f4680fab6e..36101f67ae8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -149,6 +149,18 @@ public void markAsFullyPaid() { this.paid = true; } + public void reconcileFullyPaid() { + BigDecimal waived = this.amountWaived != null ? this.amountWaived : BigDecimal.ZERO; + BigDecimal writtenOff = this.amountWrittenOff != null ? this.amountWrittenOff : BigDecimal.ZERO; + this.amountPaid = this.amount.subtract(waived).subtract(writtenOff); + this.amountOutstanding = BigDecimal.ZERO; + if (waived.compareTo(BigDecimal.ZERO) > 0) { + this.waived = true; + } else { + this.paid = true; + } + } + public boolean isFullyPaid() { return this.paid; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index d66c6bb2c63..eed2f08dc3f 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -1126,6 +1126,16 @@ public boolean isDueBalanceZero() { MathUtil.nullToZero(MathUtil.add(getPrincipal(), getInterestCharged(), getFeeChargesCharged(), getPenaltyCharges()))); } + public boolean isOutstandingBalanceNotZero(AllocationType allocationType, MonetaryCurrency currency) { + Money balance = switch (allocationType) { + case PENALTY -> this.getPenaltyChargesOutstanding(currency); + case FEE -> this.getFeeChargesOutstanding(currency); + case PRINCIPAL -> this.getPrincipalOutstanding(currency); + case INTEREST -> this.getInterestOutstanding(currency); + }; + return MathUtil.isGreaterThanZero(balance); + } + public void copyFrom(final LoanScheduleModelPeriod period) { // Reset fields and relations resetBalances(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java index 831f39fc1d9..966274a4c5e 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/GuarantorType.java @@ -25,7 +25,8 @@ public enum GuarantorType { CUSTOMER(1, "guarantor.existing.customer"), // STAFF(2, "guarantor.staff"), // - EXTERNAL(3, "guarantor.external"); // + EXTERNAL(3, "guarantor.external"), // + GROUP(4, "guarantor.existing.group"); // private final Integer value; private final String code; @@ -90,4 +91,8 @@ public boolean isStaff() { return this.value.equals(GuarantorType.STAFF.getValue()); } + public boolean isGroup() { + return this.value.equals(GuarantorType.GROUP.getValue()); + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java index e22e02f9c92..23e71ee9f0b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculator.java @@ -19,21 +19,25 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; +import lombok.RequiredArgsConstructor; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class AprCalculator { + private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; + public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal interestRatePerPeriod, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final DaysInYearType daysInYearType) { BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO; switch (interestPeriodFrequencyType) { case DAYS: - defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(daysInYearType.getValue())); + defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(getDaysInYear(daysInYearType))); break; case WEEKS: defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52)); @@ -50,7 +54,7 @@ public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequenc switch (repaymentPeriodFrequencyType) { case DAYS: - defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(daysInYearType.getValue())); + defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(getDaysInYear(daysInYearType))); break; case WEEKS: defaultAnnualNominalInterestRate = ratePerPeriod.multiply(BigDecimal.valueOf(52)); @@ -74,4 +78,24 @@ public BigDecimal calculateFrom(final PeriodFrequencyType interestPeriodFrequenc return defaultAnnualNominalInterestRate; } + /** + * Helper method to get the number of days in a year, handling ACTUAL appropriately. + * + * When daysInYearType is ACTUAL, this delegates to the PaymentPeriodsInOneYearCalculator (consistent with how + * Fineract handles ACTUAL elsewhere). For other types (DAYS_360, DAYS_364, DAYS_365), it returns the configured + * value. + * + * @param daysInYearType + * the days in year type configuration + * @return the number of days in a year + */ + private int getDaysInYear(final DaysInYearType daysInYearType) { + // When ACTUAL, delegate to calculator (consistent with LoanApplicationTerms.calculatePeriodsInOneYear) + if (daysInYearType == DaysInYearType.ACTUAL) { + return paymentPeriodsInOneYearCalculator.calculate(PeriodFrequencyType.DAYS); + } + // For DAYS_360, DAYS_364, DAYS_365: use configured value + return daysInYearType.getValue(); + } + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 297ce1b0e46..8b6a9b46933 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Set; import lombok.Getter; +import lombok.Setter; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; @@ -69,31 +70,48 @@ public final class LoanApplicationTerms { + @Getter private CurrencyData currency; + @Getter private Calendar loanCalendar; + @Getter private Integer loanTermFrequency; + @Getter private PeriodFrequencyType loanTermPeriodFrequencyType; + @Getter private Integer numberOfRepayments; private Integer actualNumberOfRepayments; + @Getter private Integer repaymentEvery; + @Getter private PeriodFrequencyType repaymentPeriodFrequencyType; private long variationDays = 0L; + @Getter private Integer fixedLength; + @Getter private Integer nthDay; + @Getter private DayOfWeekType weekDayType; + @Getter private AmortizationMethod amortizationMethod; + @Getter private InterestMethod interestMethod; private BigDecimal interestRatePerPeriod; private PeriodFrequencyType interestRatePeriodFrequencyType; + @Getter private BigDecimal annualNominalInterestRate; + @Getter private InterestCalculationPeriodMethod interestCalculationPeriodMethod; private boolean allowPartialPeriodInterestCalculation; + @Setter + @Getter private Money principal; + @Getter private LocalDate expectedDisbursementDate; private LocalDate repaymentsStartingFromDate; private LocalDate calculatedRepaymentsStartingFromDate; @@ -132,6 +150,7 @@ public final class LoanApplicationTerms { * interest should be charged. *

*/ + @Getter private LocalDate interestChargedFromDate; private Money inArrearsTolerance; @@ -140,20 +159,29 @@ public final class LoanApplicationTerms { // added private LocalDate loanEndDate; + @Getter private List disbursementDatas; private boolean multiDisburseLoan; + @Setter private BigDecimal fixedEmiAmount; + @Setter private BigDecimal fixedPrincipalAmount; + @Getter + @Setter private BigDecimal currentPeriodFixedEmiAmount; + @Getter + @Setter private BigDecimal currentPeriodFixedPrincipalAmount; + @Getter private BigDecimal actualFixedEmiAmount; + @Getter private BigDecimal maxOutstandingBalance; private Money totalInterestDue; @@ -162,34 +190,47 @@ public final class LoanApplicationTerms { private DaysInYearType daysInYearType; + @Getter private boolean interestRecalculationEnabled; + @Getter private LoanRescheduleStrategyMethod rescheduleStrategyMethod; + @Getter private InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; + @Getter private CalendarInstance restCalendarInstance; + @Getter private RecalculationFrequencyType recalculationFrequencyType; + @Getter private CalendarInstance compoundingCalendarInstance; + @Getter private RecalculationFrequencyType compoundingFrequencyType; private boolean allowCompoundingOnEod; private BigDecimal principalThresholdForLastInstalment; + @Getter private Integer installmentAmountInMultiplesOf; + @Getter private LoanPreCloseInterestCalculationStrategy preClosureInterestCalculationStrategy; + @Getter private Money approvedPrincipal; private LoanTermVariationsDataWrapper variationsDataWrapper; private Money adjustPrincipalForFlatLoans; + @Getter + @Setter private LocalDate seedDate; + @Getter private CalendarHistoryDataWrapper calendarHistoryDataWrapper; private Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled; @@ -220,18 +261,27 @@ public final class LoanApplicationTerms { private int periodsCompleted = 0; private int extraPeriods = 0; private boolean isEqualAmortization; + @Setter private Money interestTobeApproppriated; private BigDecimal fixedPrincipalPercentagePerInstallment; + @Getter + @Setter private LocalDate newScheduledDueDateStart; private boolean isDownPaymentEnabled; + @Getter private BigDecimal disbursedAmountPercentageForDownPayment; + @Getter private Money downPaymentAmount; private boolean isAutoRepaymentForDownPaymentEnabled; + @Getter private RepaymentStartDateType repaymentStartDateType; + @Getter private LocalDate submittedOnDate; + @Setter private Money disbursedPrincipal; + @Getter private LoanScheduleType loanScheduleType; private LoanScheduleProcessingType loanScheduleProcessingType; private boolean enableAccrualActivityPosting; @@ -1504,19 +1554,11 @@ private Integer calculateLastInterestGracePeriod(int periodNumber) { } public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { - boolean isPrincipalGraceApplicableForThisPeriod = false; - if (this.periodNumbersApplicableForPrincipalGrace.contains(periodNumber)) { - isPrincipalGraceApplicableForThisPeriod = true; - } - return isPrincipalGraceApplicableForThisPeriod; + return this.periodNumbersApplicableForPrincipalGrace.contains(periodNumber); } public boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { - boolean isInterestPaymentGraceApplicableForThisPeriod = false; - if (this.periodNumbersApplicableForInterestGrace.contains(periodNumber)) { - isInterestPaymentGraceApplicableForThisPeriod = true; - } - return isInterestPaymentGraceApplicableForThisPeriod; + return this.periodNumbersApplicableForInterestGrace.contains(periodNumber); } private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) { @@ -1662,12 +1704,7 @@ private Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer principalFeePeriods++; } } - Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods; - return periodsRemaining; - } - - public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) { - this.fixedPrincipalAmount = fixedPrincipalAmount; + return totalNumberOfRepaymentPeriods - periodsElapsed - principalFeePeriods; } private Money calculatePrincipalDueForInstallment(final int periodNumber, final Money totalDuePerInstallment, @@ -1715,65 +1752,17 @@ public ILoanConfigurationDetails toLoanConfigurationDetails() { repaymentEvery, numberOfRepayments, isInterestChargedFromDateSameAsDisbursalDateEnabled != null && isInterestChargedFromDateSameAsDisbursalDateEnabled, daysInYearCustomStrategy, allowPartialPeriodInterestCalculation, interestRecalculationEnabled, recalculationFrequencyType, - preClosureInterestCalculationStrategy, allowFullTermForTranche); - } - - public Integer getLoanTermFrequency() { - return this.loanTermFrequency; - } - - public PeriodFrequencyType getLoanTermPeriodFrequencyType() { - return this.loanTermPeriodFrequencyType; - } - - public Integer getRepaymentEvery() { - return this.repaymentEvery; - } - - public PeriodFrequencyType getRepaymentPeriodFrequencyType() { - return this.repaymentPeriodFrequencyType; + preClosureInterestCalculationStrategy, allowFullTermForTranche, loanScheduleProcessingType); } public LocalDate getRepaymentStartFromDate() { return this.repaymentsStartingFromDate; } - public LocalDate getInterestChargedFromDate() { - return this.interestChargedFromDate; - } - - public void setPrincipal(Money principal) { - this.principal = principal; - } - - public void setDisbursedPrincipal(Money disbursedPrincipal) { - this.disbursedPrincipal = disbursedPrincipal; - } - public LocalDate getInterestChargedFromLocalDate() { return this.interestChargedFromDate; } - public InterestMethod getInterestMethod() { - return this.interestMethod; - } - - public AmortizationMethod getAmortizationMethod() { - return this.amortizationMethod; - } - - public CurrencyData getCurrency() { - return currency; - } - - public Integer getNumberOfRepayments() { - return this.numberOfRepayments; - } - - public LocalDate getExpectedDisbursementDate() { - return this.expectedDisbursementDate; - } - public LocalDate getRepaymentsStartingFromLocalDate() { return this.repaymentsStartingFromDate; } @@ -1782,18 +1771,6 @@ public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { return this.calculatedRepaymentsStartingFromDate; } - public Money getPrincipal() { - return this.principal; - } - - public Money getApprovedPrincipal() { - return this.approvedPrincipal; - } - - public List getDisbursementDatas() { - return this.disbursementDatas; - } - public boolean isMultiDisburseLoan() { return this.multiDisburseLoan; } @@ -1803,10 +1780,6 @@ public Money getMaxOutstandingBalanceMoney() { return Money.of(getCurrency(), this.maxOutstandingBalance); } - public BigDecimal getMaxOutstandingBalance() { - return maxOutstandingBalance; - } - public BigDecimal getFixedEmiAmount() { BigDecimal fixedEmiAmount = this.fixedEmiAmount; if (getCurrentPeriodFixedEmiAmount() != null) { @@ -1815,18 +1788,6 @@ public BigDecimal getFixedEmiAmount() { return fixedEmiAmount; } - public Integer getNthDay() { - return this.nthDay; - } - - public DayOfWeekType getWeekDayType() { - return this.weekDayType; - } - - public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { - this.fixedEmiAmount = fixedEmiAmount; - } - public void resetFixedEmiAmount() { this.fixedEmiAmount = this.actualFixedEmiAmount; } @@ -1843,22 +1804,6 @@ public boolean isInterestBearingAndInterestRecalculationEnabled() { return isInterestBearing() && isInterestRecalculationEnabled(); } - public boolean isInterestRecalculationEnabled() { - return this.interestRecalculationEnabled; - } - - public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { - return this.rescheduleStrategyMethod; - } - - public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { - return this.interestRecalculationCompoundingMethod; - } - - public CalendarInstance getRestCalendarInstance() { - return this.restCalendarInstance; - } - private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) { boolean isSameAsRepaymentPeriod = false; if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { @@ -1868,14 +1813,8 @@ private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) isSameAsRepaymentPeriod = (days % 7) == 0; break; case MONTHS: - boolean isFromDateOnEndDate = false; - if (fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth()) { - isFromDateOnEndDate = true; - } - boolean isToDateOnEndDate = false; - if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) { - isToDateOnEndDate = true; - } + boolean isFromDateOnEndDate = fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth(); + boolean isToDateOnEndDate = toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth(); if (isFromDateOnEndDate && isToDateOnEndDate) { isSameAsRepaymentPeriod = true; @@ -1915,10 +1854,6 @@ private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { return numberOfPeriods; } - public RecalculationFrequencyType getRecalculationFrequencyType() { - return this.recalculationFrequencyType; - } - public void updateNumberOfRepayments(final Integer numberOfRepayments) { this.numberOfRepayments = numberOfRepayments; this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); @@ -1941,14 +1876,13 @@ public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) { if (annualNominalInterestRate != null) { + if (this.annualNominalInterestRate == null || annualNominalInterestRate.compareTo(this.annualNominalInterestRate) != 0) { + this.fixedEmiAmount = null; + } this.annualNominalInterestRate = annualNominalInterestRate; } } - public BigDecimal getAnnualNominalInterestRate() { - return this.annualNominalInterestRate; - } - public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { if (interestChargedFromDate != null) { this.interestChargedFromDate = interestChargedFromDate; @@ -1965,30 +1899,6 @@ public void updateTotalInterestDue(Money totalInterestDue) { this.totalInterestDue = totalInterestDue; } - public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { - return this.interestCalculationPeriodMethod; - } - - public LoanPreCloseInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { - return this.preClosureInterestCalculationStrategy; - } - - public CalendarInstance getCompoundingCalendarInstance() { - return this.compoundingCalendarInstance; - } - - public RecalculationFrequencyType getCompoundingFrequencyType() { - return this.compoundingFrequencyType; - } - - public BigDecimal getActualFixedEmiAmount() { - return this.actualFixedEmiAmount; - } - - public Calendar getLoanCalendar() { - return loanCalendar; - } - public BigDecimal getFixedPrincipalAmount() { BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount; if (getCurrentPeriodFixedPrincipalAmount() != null) { @@ -2001,34 +1911,10 @@ public LoanTermVariationsDataWrapper getLoanTermVariations() { return this.variationsDataWrapper; } - public BigDecimal getCurrentPeriodFixedEmiAmount() { - return this.currentPeriodFixedEmiAmount; - } - - public void setCurrentPeriodFixedEmiAmount(BigDecimal currentPeriodFixedEmiAmount) { - this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount; - } - - public BigDecimal getCurrentPeriodFixedPrincipalAmount() { - return this.currentPeriodFixedPrincipalAmount; - } - - public void setCurrentPeriodFixedPrincipalAmount(BigDecimal currentPeriodFixedPrincipalAmount) { - this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount; - } - public Integer fetchNumberOfRepaymentsAfterExceptions() { return this.actualNumberOfRepayments; } - public LocalDate getSeedDate() { - return this.seedDate; - } - - public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() { - return this.calendarHistoryDataWrapper; - } - public Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled() { return this.isInterestChargedFromDateSameAsDisbursalDateEnabled; } @@ -2150,10 +2036,6 @@ public void updateTotalInterestAccounted(Money totalInterestAccounted) { this.totalInterestAccounted = totalInterestAccounted; } - public void setSeedDate(LocalDate seedDate) { - this.seedDate = seedDate; - } - public boolean isEqualAmortization() { return isEqualAmortization; } @@ -2170,10 +2052,6 @@ public Money getInterestTobeApproppriated() { return interestTobeApproppriated == null ? this.principal.zero() : interestTobeApproppriated; } - public void setInterestTobeApproppriated(Money interestTobeApproppriated) { - this.interestTobeApproppriated = interestTobeApproppriated; - } - public Boolean isInterestTobeApproppriated() { return interestTobeApproppriated != null && interestTobeApproppriated.isGreaterThanZero(); } @@ -2186,46 +2064,10 @@ public boolean isPrincipalCompoundingDisabledForOverdueLoans() { return isPrincipalCompoundingDisabledForOverdueLoans; } - public LocalDate getNewScheduledDueDateStart() { - return newScheduledDueDateStart; - } - - public void setNewScheduledDueDateStart(LocalDate newScheduledDueDateStart) { - this.newScheduledDueDateStart = newScheduledDueDateStart; - } - public boolean isDownPaymentEnabled() { return isDownPaymentEnabled; } - public BigDecimal getDisbursedAmountPercentageForDownPayment() { - return disbursedAmountPercentageForDownPayment; - } - - public RepaymentStartDateType getRepaymentStartDateType() { - return repaymentStartDateType; - } - - public LocalDate getSubmittedOnDate() { - return submittedOnDate; - } - - public Integer getInstallmentAmountInMultiplesOf() { - return installmentAmountInMultiplesOf; - } - - public LoanScheduleType getLoanScheduleType() { - return loanScheduleType; - } - - public Money getDownPaymentAmount() { - return downPaymentAmount; - } - - public Integer getFixedLength() { - return fixedLength; - } - public LocalDate calculateMaxDateForFixedLength() { final LocalDate startDate = getRepaymentStartDate(); LocalDate maxDateForFixedLength = null; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java index 017abc198ac..1312be79dab 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestData.java @@ -20,12 +20,14 @@ import java.time.LocalDate; import java.util.Collection; +import lombok.Getter; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; /** * Immutable data object representing loan reschedule request data. **/ +@Getter public final class LoanRescheduleRequestData { private final Long id; @@ -126,41 +128,6 @@ public static LoanRescheduleRequestData instance(Long id, Long loanId, LoanResch rescheduleReasonCodeValue); } - /** - * @return the id - */ - public Long getId() { - return id; - } - - /** - * @return the loanId - */ - public Long getLoanId() { - return loanId; - } - - /** - * @return the statusEnum - */ - public LoanRescheduleRequestStatusEnumData getStatusEnum() { - return statusEnum; - } - - /** - * @return the reschedule from installment number - */ - public Integer getRescheduleFromInstallment() { - return rescheduleFromInstallment; - } - - /** - * @return the reschedule from date - */ - public LocalDate getRescheduleFromDate() { - return rescheduleFromDate; - } - /** * @return the rescheduleReasonCodeValueId */ @@ -168,41 +135,6 @@ public CodeValueData getRescheduleReasonCodeValueId() { return rescheduleReasonCodeValue; } - /** - * @return the rescheduleReasonText - */ - public String getRescheduleReasonComment() { - return rescheduleReasonComment; - } - - /** - * @return the timeline - **/ - public LoanRescheduleRequestTimelineData getTimeline() { - return this.timeline; - } - - /** - * @return the clientName - */ - public String getClientName() { - return clientName; - } - - /** - * @return the loanAccountNumber - */ - public String getLoanAccountNumber() { - return loanAccountNumber; - } - - /** - * @return the clientId - */ - public Long getClientId() { - return clientId; - } - /** * @return the recalculateInterest */ diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java index 087cdd6c464..6685101639c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestStatusEnumData.java @@ -18,11 +18,15 @@ */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data; +import lombok.Getter; +import lombok.experimental.Accessors; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; /** * Immutable data object represent loan reschedule request status enumerations. **/ +@Getter +@Accessors(fluent = true) public class LoanRescheduleRequestStatusEnumData { private final Long id; @@ -46,18 +50,6 @@ public LoanRescheduleRequestStatusEnumData(Long id, String code, String value) { this.rejected = Long.valueOf(LoanStatus.REJECTED.getValue()).equals(this.id); } - public Long id() { - return this.id; - } - - public String code() { - return this.code; - } - - public String value() { - return this.value; - } - public boolean isPendingApproval() { return this.pendingApproval; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java index 8715f016435..d9b70f18fac 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestTimelineData.java @@ -19,11 +19,13 @@ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data; import java.time.LocalDate; +import lombok.Data; /** * Immutable data object represent the timeline events of a loan reschedule request **/ @SuppressWarnings("unused") +@Data public class LoanRescheduleRequestTimelineData { private final LocalDate submittedOnDate; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java index 06a5428ac46..b487c862c40 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModelRepaymentPeriod.java @@ -20,9 +20,11 @@ import java.math.BigDecimal; import java.time.LocalDate; +import lombok.Setter; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; +@Setter public final class LoanRescheduleModelRepaymentPeriod implements LoanRescheduleModalPeriod { private int periodNumber; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java index b8211af7e41..ef743f0038e 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import lombok.Getter; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -40,6 +41,7 @@ @Entity @Table(name = "m_loan_reschedule_request") +@Getter public class LoanRescheduleRequest extends AbstractPersistableCustom { @ManyToOne @@ -129,83 +131,6 @@ public static LoanRescheduleRequest instance(final Loan loan, final Integer stat rejectedOnDate, rejectedByUser); } - /** - * @return the reschedule request loan object - **/ - public Loan getLoan() { - return this.loan; - } - - /** - * @return the status enum - **/ - public Integer getStatusEnum() { - return this.statusEnum; - } - - /** - * @return installment number of the rescheduling start point - **/ - public Integer getRescheduleFromInstallment() { - return this.rescheduleFromInstallment; - } - - /** - * @return due date of the rescheduling start point - **/ - public LocalDate getRescheduleFromDate() { - return this.rescheduleFromDate; - } - - /** - * @return the reschedule reason code value object - **/ - public CodeValue getRescheduleReasonCodeValue() { - return this.rescheduleReasonCodeValue; - } - - /** - * @return the reschedule reason comment added by the "submittedByUser" - **/ - public String getRescheduleReasonComment() { - return this.rescheduleReasonComment; - } - - /** - * @return the date the request was submitted - **/ - public LocalDate getSubmittedOnDate() { - return this.submittedOnDate; - } - - /** - * @return the user that submitted the request - **/ - public AppUser getSubmittedByUser() { - return this.submittedByUser; - } - - /** - * @return the date the request was approved - **/ - public LocalDate getApprovedOnDate() { - return this.approvedOnDate; - } - - /** - * @return the user that approved the request - **/ - public AppUser getApprovedByUser() { - return this.approvedByUser; - } - - /** - * @return the date the request was rejected - **/ - public LocalDate getRejectedOnDate() { - return this.rejectedOnDate; - } - /** * @return the recalculate interest option (true/false) **/ @@ -219,13 +144,6 @@ public Boolean getRecalculateInterest() { return recalculateInterest; } - /** - * @return the user that rejected the request - **/ - public AppUser getRejectedByUser() { - return this.rejectedByUser; - } - /** * change the status of the loan reschedule request to approved, also updating the approvedByUser and approvedOnDate * properties @@ -268,10 +186,6 @@ public void updateLoanRescheduleRequestToTermVariationMappings(final List getLoanRescheduleRequestToTermVariationMappings() { - return this.loanRescheduleRequestToTermVariationMappings; - } - public LoanTermVariations getInterestRateFromInstallmentTermVariationIfExists() { return this.loanRescheduleRequestToTermVariationMappings.stream() .map(LoanRescheduleRequestToTermVariationMapping::getLoanTermVariations) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java index c15689549df..04fed6458f8 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java @@ -23,6 +23,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.persistence.FlushModeHandler; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -35,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; @@ -121,9 +123,34 @@ public void refreshSummaryAndBalancesForDisbursedLoan(final Loan loan) { final Money capitalizedIncomeAdjustment = capitalizedIncomeBalanceService.calculateCapitalizedIncomeAdjustment(loan); loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(), capitalizedIncome, capitalizedIncomeAdjustment); + reconcileChargeStatusWithSummary(loan); updateLoanOutstandingBalances(loan); } + private void reconcileChargeStatusWithSummary(final Loan loan) { + final LoanSummary summary = loan.getSummary(); + final Set charges = loan.getLoanCharges(); + if (summary == null || charges == null) { + return; + } + if (MathUtil.isZero(summary.getTotalFeeChargesOutstanding())) { + for (final LoanCharge charge : charges) { + if (charge.isActive() && charge.isFeeCharge() && !charge.isPaid() && !charge.isWaived() + && MathUtil.isGreaterThanZero(charge.getAmount())) { + charge.reconcileFullyPaid(); + } + } + } + if (MathUtil.isZero(summary.getTotalPenaltyChargesOutstanding())) { + for (final LoanCharge charge : charges) { + if (charge.isActive() && charge.isPenaltyCharge() && !charge.isPaid() && !charge.isWaived() + && MathUtil.isGreaterThanZero(charge.getAmount())) { + charge.reconcileFullyPaid(); + } + } + } + } + private Money calculateTotalRecoveredPayments(Loan loan) { // in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments final BigDecimal totalRecoveryAmount = loanTransactionRepository.calculateTotalRecoveryPaymentAmount(loan); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsDetailsApiResource.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsDetailsApiResource.java new file mode 100644 index 00000000000..1225006b99a --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsDetailsApiResource.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriInfo; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadBasicDetailsService; +import org.springframework.stereotype.Component; + +@Path("/v1/loanproducts") +@Component +@Tag(name = "Loan Products Details", description = "Loan product basic details to be listed") +@RequiredArgsConstructor +public class LoanProductsDetailsApiResource { + + private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOANPRODUCT"; + private final PlatformSecurityContext context; + private final List loanProductReadBasicDetailsServices; + + @GET + @Path("basic-details") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Loan Products with basic details", description = "Lists Loan Products with basic details to be listed") + public Collection fetchProducts(@Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); + + Collection products = new ArrayList<>(); + loanProductReadBasicDetailsServices.forEach(service -> products.addAll(service.retrieveProducts())); + return products; + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java index 977e9a513cb..3fdede017f0 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanConfigurationDetails.java @@ -25,6 +25,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.ILoanConfigurationDetails; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; @@ -60,6 +61,8 @@ public class LoanConfigurationDetails implements ILoanConfigurationDetails { private final LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy; @Getter private final boolean allowFullTermForTranche; + @Getter + private final LoanScheduleProcessingType loanScheduleProcessingType; public LoanConfigurationDetails(CurrencyData currency, BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate, Integer interestChargingGrace, Integer interestPaymentGrace, Integer principalGrace, @@ -69,7 +72,8 @@ public LoanConfigurationDetails(CurrencyData currency, BigDecimal interestRatePe Integer numberOfRepayments, boolean interestRecognitionOnDisbursementDate, DaysInYearCustomStrategyType daysInYearCustomStrategy, boolean allowPartialPeriodInterestCalculation, boolean isInterestRecalculationEnabled, RecalculationFrequencyType restFrequencyType, - LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy, boolean allowFullTermForTranche) { + LoanPreCloseInterestCalculationStrategy preCloseInterestCalculationStrategy, boolean allowFullTermForTranche, + LoanScheduleProcessingType loanScheduleProcessingType) { this.currency = currency; this.interestRatePerPeriod = interestRatePerPeriod; this.annualNominalInterestRate = annualNominalInterestRate; @@ -92,6 +96,7 @@ public LoanConfigurationDetails(CurrencyData currency, BigDecimal interestRatePe this.restFrequencyType = restFrequencyType; this.preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy; this.allowFullTermForTranche = allowFullTermForTranche; + this.loanScheduleProcessingType = loanScheduleProcessingType; } private Integer defaultToNullIfZero(final Integer value) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBasicDetailsData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBasicDetailsData.java new file mode 100644 index 00000000000..b5b7c83aaa1 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductBasicDetailsData.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.data; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.organisation.monetary.data.CurrencyData; + +@Data +@RequiredArgsConstructor +public class LoanProductBasicDetailsData { + + private final String productType; + private final Long id; + private final String name; + private final String shortName; + private final String description; + private final CurrencyData currency; + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java index 4ec89f0fd0c..79fe75bc9e5 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/ILoanConfigurationDetails.java @@ -22,6 +22,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; /** * Represents the bare minimum repayment details needed for activities related to generating repayment schedules. @@ -75,4 +76,6 @@ public interface ILoanConfigurationDetails { LoanPreCloseInterestCalculationStrategy getPreCloseInterestCalculationStrategy(); boolean isAllowFullTermForTranche(); + + LoanScheduleProcessingType getLoanScheduleProcessingType(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java index 78314991b2c..6a35428a311 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRepository.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanproduct.domain; +import java.time.LocalDate; import java.util.List; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; @@ -41,4 +42,7 @@ public interface LoanProductRepository extends JpaRepository, @Override @Query("SELECT CASE WHEN COUNT(loanProduct)>0 THEN TRUE ELSE FALSE END FROM LoanProduct loanProduct WHERE loanProduct.id = :loanProductId") boolean existsById(@NonNull @Param("loanProductId") Long loanProductId); + + @Query("select loanProduct from LoanProduct loanProduct where loanProduct.closeDate is null or loanProduct.closeDate >= :businessDate") + List fetchActiveLoanProducts(LocalDate businessDate); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/LoanProductBasicDetailsMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/LoanProductBasicDetailsMapper.java new file mode 100644 index 00000000000..26f1d4a6bed --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/mapper/LoanProductBasicDetailsMapper.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.mapper; + +import java.util.List; +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class) +public interface LoanProductBasicDetailsMapper { + + @Mapping(target = "productType", constant = "loan") + @Mapping(target = "currency", source = "loanProductRelatedDetail.currency", qualifiedByName = "currencyData") + LoanProductBasicDetailsData map(LoanProduct source); + + List map(List source); + + @Named("currencyData") + default CurrencyData currencyData(final MonetaryCurrency currency) { + return currency.toData(); + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsService.java new file mode 100644 index 00000000000..b790823e811 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsService.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.service; + +import java.util.Collection; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; + +public interface LoanProductReadBasicDetailsService { + + Collection retrieveProducts(); + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsServiceImpl.java new file mode 100644 index 00000000000..b966dc6b597 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadBasicDetailsServiceImpl.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.service; + +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; +import org.apache.fineract.portfolio.loanproduct.mapper.LoanProductBasicDetailsMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class LoanProductReadBasicDetailsServiceImpl implements LoanProductReadBasicDetailsService { + + private final LoanProductBasicDetailsMapper loanProductBasicDetailsMapper; + private final LoanProductRepository loanProductRepository; + + @Override + public Collection retrieveProducts() { + return loanProductBasicDetailsMapper.map(loanProductRepository.fetchActiveLoanProducts(DateUtils.getBusinessLocalDate())); + } + +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java index d6ddef51a81..5536b45b33d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformService.java @@ -57,4 +57,5 @@ public interface LoanProductReadPlatformService { Collection retrieveCreditAllocationData(Long loanProductId); LoanProductData retrieveLoanProductFloatingDetails(Long loanProductId); + } diff --git a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculatorTest.java b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculatorTest.java new file mode 100644 index 00000000000..f11129deee6 --- /dev/null +++ b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AprCalculatorTest.java @@ -0,0 +1,334 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.common.domain.DaysInYearType; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +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.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AprCalculatorTest { + + private static final int PRECISION = 19; + private static final MockedStatic MONEY_HELPER = mockStatic(MoneyHelper.class); + private static final MathContext MATH_CONTEXT = new MathContext(PRECISION, RoundingMode.HALF_EVEN); + + @Mock + private PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator; + + private AprCalculator aprCalculator; + + @BeforeAll + static void init() { + MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); + MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(MATH_CONTEXT); + } + + @BeforeEach + void setUp() { + aprCalculator = new AprCalculator(paymentPeriodsInOneYearCalculator); + } + + @AfterAll + static void tearDown() { + MONEY_HELPER.close(); + } + + /** + * Test DAYS frequency with DaysInYearType.ACTUAL + * + * This test verifies the fix for FINERACT-2492 where ACTUAL was incorrectly using value 1 + * instead of delegating to PaymentPeriodsInOneYearCalculator which returns 365. + */ + @Test + void testCalculateFrom_DaysFrequency_WithActualDaysInYear() { + // Given + when(paymentPeriodsInOneYearCalculator.calculate(PeriodFrequencyType.DAYS)).thenReturn(365); + + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); // 10% per day + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.ACTUAL; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, + PeriodFrequencyType.DAYS, daysInYearType); + + // Then + // Annual rate should be 10% * 365 = 3650% + assertEquals(0, BigDecimal.valueOf(3650.0).compareTo(annualRate), + "Annual rate should be interestRatePerPeriod * 365 for ACTUAL"); + verify(paymentPeriodsInOneYearCalculator).calculate(PeriodFrequencyType.DAYS); + } + + /** + * Test WHOLE_TERM frequency with DAYS repayment and DaysInYearType.ACTUAL + * + * This is the exact scenario from FINERACT-2492 bug report. + */ + @Test + void testCalculateFrom_WholeTermFrequency_WithDaysRepaymentAndActualDaysInYear() { + // Given + when(paymentPeriodsInOneYearCalculator.calculate(PeriodFrequencyType.DAYS)).thenReturn(365); + + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); // 10% whole term + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.WHOLE_TERM; + Integer numberOfRepayments = 3; + Integer repaymentEvery = 1; + PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.ACTUAL; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments, + repaymentEvery, repaymentFrequencyType, daysInYearType); + + // Then + // ratePerPeriod = 10% / (3 * 1) = 3.33333333% + // annualRate = 3.33333333% * 365 = 1216.66666667% + BigDecimal expectedAnnualRate = BigDecimal.valueOf(10.0).divide(BigDecimal.valueOf(3), 8, java.math.RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(365)); + + assertEquals(0, expectedAnnualRate.compareTo(annualRate), "Annual rate calculation should use 365 days for ACTUAL"); + verify(paymentPeriodsInOneYearCalculator).calculate(PeriodFrequencyType.DAYS); + } + + /** + * Test DAYS frequency with DaysInYearType.DAYS_360 + * + * Verify that DAYS_360 works correctly and uses value 360 directly. + */ + @Test + void testCalculateFrom_DaysFrequency_WithDays360() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.DAYS_360; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.DAYS, + daysInYearType); + + // Then + // Annual rate should be 10% * 360 = 3600% + assertEquals(0, BigDecimal.valueOf(3600.0).compareTo(annualRate), "Annual rate should use 360 days for DAYS_360"); + } + + /** + * Test DAYS frequency with DaysInYearType.DAYS_364 + * + * Verify that DAYS_364 works correctly and uses value 364 directly. + */ + @Test + void testCalculateFrom_DaysFrequency_WithDays364() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.DAYS_364; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.DAYS, + daysInYearType); + + // Then + // Annual rate should be 10% * 364 = 3640% + assertEquals(0, BigDecimal.valueOf(3640.0).compareTo(annualRate), "Annual rate should use 364 days for DAYS_364"); + } + + /** + * Test DAYS frequency with DaysInYearType.DAYS_365 + * + * Verify that DAYS_365 works correctly and uses value 365 directly. + */ + @Test + void testCalculateFrom_DaysFrequency_WithDays365() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.DAYS_365; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.DAYS, + daysInYearType); + + // Then + // Annual rate should be 10% * 365 = 3650% + assertEquals(0, BigDecimal.valueOf(3650.0).compareTo(annualRate), "Annual rate should use 365 days for DAYS_365"); + } + + /** + * Test WHOLE_TERM frequency with WEEKS repayment and DaysInYearType.ACTUAL + * + * Verify that ACTUAL doesn't affect non-DAYS repayment frequencies. + */ + @Test + void testCalculateFrom_WholeTermFrequency_WithWeeksRepaymentAndActualDaysInYear() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.WHOLE_TERM; + Integer numberOfRepayments = 4; + Integer repaymentEvery = 1; + PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.WEEKS; + DaysInYearType daysInYearType = DaysInYearType.ACTUAL; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments, + repaymentEvery, repaymentFrequencyType, daysInYearType); + + // Then + // ratePerPeriod = 10% / (4 * 1) = 2.5% + // annualRate = 2.5% * 52 = 130% + BigDecimal expectedAnnualRate = BigDecimal.valueOf(10.0).divide(BigDecimal.valueOf(4), 8, java.math.RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(52)); + + assertEquals(0, expectedAnnualRate.compareTo(annualRate), "Annual rate for WEEKS should use 52 weeks"); + } + + /** + * Test WHOLE_TERM frequency with MONTHS repayment and DaysInYearType.ACTUAL + * + * Verify that ACTUAL doesn't affect non-DAYS repayment frequencies. + */ + @Test + void testCalculateFrom_WholeTermFrequency_WithMonthsRepaymentAndActualDaysInYear() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(12.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.WHOLE_TERM; + Integer numberOfRepayments = 6; + Integer repaymentEvery = 1; + PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.MONTHS; + DaysInYearType daysInYearType = DaysInYearType.ACTUAL; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments, + repaymentEvery, repaymentFrequencyType, daysInYearType); + + // Then + // ratePerPeriod = 12% / (6 * 1) = 2% + // annualRate = 2% * 12 = 24% + BigDecimal expectedAnnualRate = BigDecimal.valueOf(12.0).divide(BigDecimal.valueOf(6), 8, java.math.RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(12)); + + assertEquals(0, expectedAnnualRate.compareTo(annualRate), "Annual rate for MONTHS should use 12 months"); + } + + /** + * Test WEEKS frequency + */ + @Test + void testCalculateFrom_WeeksFrequency() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(2.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.WEEKS; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.WEEKS, + DaysInYearType.ACTUAL); + + // Then + // Annual rate should be 2% * 52 = 104% + assertEquals(0, BigDecimal.valueOf(104.0).compareTo(annualRate), "Annual rate for WEEKS should multiply by 52"); + } + + /** + * Test MONTHS frequency + */ + @Test + void testCalculateFrom_MonthsFrequency() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(2.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.MONTHS; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.MONTHS, + DaysInYearType.ACTUAL); + + // Then + // Annual rate should be 2% * 12 = 24% + assertEquals(0, BigDecimal.valueOf(24.0).compareTo(annualRate), "Annual rate for MONTHS should multiply by 12"); + } + + /** + * Test YEARS frequency + */ + @Test + void testCalculateFrom_YearsFrequency() { + // Given + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(5.0); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.YEARS; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, 1, 1, PeriodFrequencyType.YEARS, + DaysInYearType.ACTUAL); + + // Then + // Annual rate should be 5% * 1 = 5% + assertEquals(0, BigDecimal.valueOf(5.0).compareTo(annualRate), "Annual rate for YEARS should multiply by 1"); + } + + /** + * Test bug scenario with realistic values from FINERACT-2492 + * + * Principal: 3,400 + * Interest rate: 10% WHOLE_TERM + * Repayments: 3 daily + * Expected interest per installment: 113.33 + */ + @Test + void testCalculateFrom_BugReproductionScenario() { + // Given + when(paymentPeriodsInOneYearCalculator.calculate(PeriodFrequencyType.DAYS)).thenReturn(365); + + BigDecimal interestRatePerPeriod = BigDecimal.valueOf(10.0); // 10% whole term + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.WHOLE_TERM; + Integer numberOfRepayments = 3; + Integer repaymentEvery = 1; + PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.DAYS; + DaysInYearType daysInYearType = DaysInYearType.ACTUAL; + + // When + BigDecimal annualRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod, numberOfRepayments, + repaymentEvery, repaymentFrequencyType, daysInYearType); + + // Then + // The bug was causing annual rate to be 3.333% instead of 1216.667% + // Verify it's much greater than 100 (definitely not 3.333) + assertEquals(true, annualRate.compareTo(BigDecimal.valueOf(1000)) > 0, + "Annual rate should be > 1000% (bug was producing 3.333%)"); + + // Verify exact expected value: 10/3 * 365 = 1216.66666667 + BigDecimal expectedRate = BigDecimal.valueOf(10.0).divide(BigDecimal.valueOf(3), 8, java.math.RoundingMode.HALF_EVEN) + .multiply(BigDecimal.valueOf(365)); + assertEquals(0, expectedRate.compareTo(annualRate), "Annual rate should be exactly 1216.67%"); + } +} diff --git a/fineract-mix/build.gradle b/fineract-mix/build.gradle new file mode 100644 index 00000000000..bbde3766997 --- /dev/null +++ b/fineract-mix/build.gradle @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +description = 'Fineract Mix' + +apply plugin: 'java' +apply plugin: 'eclipse' + +compileJava { + dependsOn ':fineract-avro-schemas:buildJavaSdk' +} + +configurations { + providedRuntime // needed for Spring Boot executable WAR + providedCompile + compile() { + exclude module: 'hibernate-entitymanager' + exclude module: 'hibernate-validator' + exclude module: 'activation' + exclude module: 'bcmail-jdk14' + exclude module: 'bcprov-jdk14' + exclude module: 'bctsp-jdk14' + exclude module: 'c3p0' + exclude module: 'stax-api' + exclude module: 'jaxb-api' + exclude module: 'jaxb-impl' + exclude module: 'jboss-logging' + exclude module: 'itext-rtf' + exclude module: 'classworlds' + } + runtime +} + +apply from: 'dependencies.gradle' + +// Configuration for the modernizer plugin +// https://github.com/andygoossens/gradle-modernizer-plugin +modernizer { + ignoreClassNamePatterns = [ + '.*AbstractPersistableCustom', + '.*EntityTables', + '.*domain.*' + ] +} + +// If we are running Gradle within Eclipse to enhance classes with OpenJPA, +// set the classes directory to point to Eclipse's default build directory +if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') { + sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-core/bin/main") +} + +if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) { + sourceSets { + test { + java { + exclude '**/core/boot/tests/**' + } + } + } +} diff --git a/fineract-mix/dependencies.gradle b/fineract-mix/dependencies.gradle new file mode 100644 index 00000000000..cf823616690 --- /dev/null +++ b/fineract-mix/dependencies.gradle @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + // Never use "compile" scope, but make all dependencies either 'implementation', 'runtimeOnly' or 'testCompile'. + // Note that we never use 'api', because Fineract at least currently is a simple monolithic application ("WAR"), not a library. + // We also (normally should have) no need to ever use 'compileOnly'. + + // implementation dependencies are directly used (compiled against) in src/main (and src/test) + // + implementation( + project(path: ':fineract-core'), + project(path: ':fineract-command'), + ) + + implementation( + 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-validation', + 'org.springframework.boot:spring-boot-starter-security', + 'org.springframework.boot:spring-boot-starter-data-jdbc', + 'jakarta.ws.rs:jakarta.ws.rs-api', + + 'org.apache.commons:commons-lang3', + + 'com.google.guava:guava', + // TODO: try to get rid of this as soon as we get hands on the MIX XSD file from 2009; then we can use JAXB + 'com.google.code.gson:gson', + + 'com.github.spotbugs:spotbugs-annotations', + 'io.swagger.core.v3:swagger-annotations-jakarta', + + 'org.mapstruct:mapstruct', + + 'io.github.resilience4j:resilience4j-spring-boot3', + ) + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'org.mapstruct:mapstruct-processor' + implementation('org.dom4j:dom4j') { + exclude group: 'javax.xml.bind' + } + // testCompile dependencies are ONLY used in src/test, not src/main. + // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! + // + testImplementation( 'io.github.classgraph:classgraph' ) + testImplementation ('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'com.jayway.jsonpath', module: 'json-path' + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + exclude group: 'jakarta.activation' + exclude group: 'javax.activation' + exclude group: 'org.skyscreamer' + } + testImplementation ('org.mockito:mockito-inline') +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java similarity index 78% rename from fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java index bbdccc2b481..b381b587049 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixReportApiResource.java @@ -26,27 +26,28 @@ import jakarta.ws.rs.core.MediaType; import java.sql.Date; import lombok.RequiredArgsConstructor; -import org.apache.fineract.mix.data.XBRLData; -import org.apache.fineract.mix.service.XBRLBuilder; -import org.apache.fineract.mix.service.XBRLResultService; +import org.apache.fineract.mix.service.MixReportXBRLBuilder; +import org.apache.fineract.mix.service.MixReportXBRLResultService; import org.springframework.stereotype.Component; @Path("/v1/mixreport") @Component -@Tag(name = "Mix Report", description = "") +@Tag(name = "Mix Report", description = """ + """) @RequiredArgsConstructor public class MixReportApiResource { - private final XBRLResultService xbrlResultService; - private final XBRLBuilder xbrlBuilder; + private final MixReportXBRLResultService xbrlResultService; + private final MixReportXBRLBuilder xbrlBuilder; @GET @Produces({ MediaType.APPLICATION_XML }) public String retrieveXBRLReport(@QueryParam("startDate") final Date startDate, @QueryParam("endDate") final Date endDate, @QueryParam("currency") final String currency) { - final XBRLData data = this.xbrlResultService.getXBRLResult(startDate, endDate, currency); + final var data = xbrlResultService.getXBRLResult(startDate, endDate, currency); + // TODO: make this type safe? return this.xbrlBuilder.build(data); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java similarity index 79% rename from fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java index f53b3171958..bb036cd8f50 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyApiResource.java @@ -26,9 +26,8 @@ import jakarta.ws.rs.core.MediaType; import java.util.List; import lombok.RequiredArgsConstructor; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.mix.data.MixTaxonomyData; -import org.apache.fineract.mix.service.MixTaxonomyReadPlatformService; +import org.apache.fineract.mix.service.MixTaxonomyReadService; import org.springframework.stereotype.Component; @Path("/v1/mixtaxonomy") @@ -37,17 +36,12 @@ @RequiredArgsConstructor public class MixTaxonomyApiResource { - private final PlatformSecurityContext context; - private final MixTaxonomyReadPlatformService readTaxonomyService; + private final MixTaxonomyReadService readTaxonomyService; @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public List retrieveAll() { - - // FIXME - KW - no check for permission to read mix taxonomy data. - this.context.authenticatedUser(); - return readTaxonomyService.retrieveAll(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java similarity index 53% rename from fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java index 1b259780e64..7ad31ccbada 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/api/MixTaxonomyMappingApiResource.java @@ -25,16 +25,14 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import java.util.function.Supplier; import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.domain.CommandWrapper; -import org.apache.fineract.commands.service.CommandWrapperBuilder; -import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.command.core.CommandPipeline; +import org.apache.fineract.mix.command.MixTaxonomyMappingUpdateCommand; import org.apache.fineract.mix.data.MixTaxonomyMappingData; -import org.apache.fineract.mix.data.MixTaxonomyRequest; -import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformService; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateResponse; +import org.apache.fineract.mix.service.MixTaxonomyMappingReadService; import org.springframework.stereotype.Component; @Path("/v1/mixmapping") @@ -43,31 +41,31 @@ @RequiredArgsConstructor public class MixTaxonomyMappingApiResource { - private final PlatformSecurityContext context; - private final ToApiJsonSerializer toApiJsonSerializer; - private final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final MixTaxonomyMappingReadService readTaxonomyMappingService; + private final CommandPipeline commandPipeline; @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public MixTaxonomyMappingData retrieveTaxonomyMapping() { - this.context.authenticatedUser(); return this.readTaxonomyMappingService.retrieveTaxonomyMapping(); } @PUT @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String updateTaxonomyMapping(final MixTaxonomyRequest mixTaxonomyRequest) { - // TODO support multiple configuration file loading - final Long mappingId = (long) 1; - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateTaxonomyMapping(mappingId) - .withJson(toApiJsonSerializer.serialize(mixTaxonomyRequest)).build(); + public MixTaxonomyMappingUpdateResponse updateTaxonomyMapping(final MixTaxonomyMappingUpdateRequest request) { + // TODO support multiple configuration file loading; this is the legacy behavior + if (request.getId() == null) { + request.setId(1L); + } - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + final var command = new MixTaxonomyMappingUpdateCommand(); - return this.toApiJsonSerializer.serialize(result); - } + command.setPayload(request); + + final Supplier response = commandPipeline.send(command); + return response.get(); + } } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/command/MixTaxonomyMappingUpdateCommand.java b/fineract-mix/src/main/java/org/apache/fineract/mix/command/MixTaxonomyMappingUpdateCommand.java new file mode 100644 index 00000000000..fa59fe6a50f --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/command/MixTaxonomyMappingUpdateCommand.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class MixTaxonomyMappingUpdateCommand extends Command {} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLContextData.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLContextData.java new file mode 100644 index 00000000000..d70d6abe5ff --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLContextData.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class MixReportXBRLContextData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String dimensionType; + private String dimension; + private Integer periodType; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLData.java similarity index 76% rename from fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLData.java index f53c1785c7b..a6f60929ac9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/XBRLData.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLData.java @@ -18,19 +18,28 @@ */ package org.apache.fineract.mix.data; +import java.io.Serial; +import java.io.Serializable; import java.math.BigDecimal; import java.sql.Date; -import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor @Accessors(chain = true) -public class XBRLData { +public class MixReportXBRLData implements Serializable { - private HashMap resultMap; + @Serial + private static final long serialVersionUID = 1L; + + private Map resultMap; private Date startDate; private Date endDate; private String currency; diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLNamespaceData.java similarity index 79% rename from fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLNamespaceData.java index 954301a560d..6e256bc663d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/NamespaceData.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixReportXBRLNamespaceData.java @@ -18,18 +18,25 @@ */ package org.apache.fineract.mix.data; +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor @Accessors(chain = true) -public class NamespaceData { +public class MixReportXBRLNamespaceData implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; - @SuppressWarnings("unused") private Long id; - @SuppressWarnings("unused") private String prefix; private String url; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java similarity index 78% rename from fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java index 57fc937da4f..857df912a33 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyData.java @@ -19,29 +19,37 @@ package org.apache.fineract.mix.data; import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor @Accessors(chain = true) -public class MixTaxonomyData { +public class MixTaxonomyData implements Serializable { public static final Integer PORTFOLIO = 0; - public static final Integer BALANCESHEET = 1; + public static final Integer BALANCE_SHEET = 1; public static final Integer INCOME = 2; public static final Integer EXPENSE = 3; - @SuppressWarnings("unused") + @Serial + private static final long serialVersionUID = 1L; + private Long id; private String name; private String namespace; private String dimension; private Integer type; - @SuppressWarnings("unused") private String description; + // TODO: why is this different from the PORTFOLIO constant? This doesn't seem right! @JsonIgnore public boolean isPortfolio() { return this.type == 5; diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java similarity index 90% rename from fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java index 7d52bba70b0..26e71b3a581 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingData.java @@ -18,17 +18,20 @@ */ package org.apache.fineract.mix.data; -import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor @Accessors(chain = true) -@JsonInclude(JsonInclude.Include.NON_NULL) public class MixTaxonomyMappingData { private String identifier; private String config; + private String currency; } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateRequest.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateRequest.java new file mode 100644 index 00000000000..eeea823cd52 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateRequest.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.data; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MixTaxonomyMappingUpdateRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + private String identifier; + private String config; + private String currency; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyRequest.java b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateResponse.java similarity index 89% rename from fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyRequest.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateResponse.java index 4a37e7262d9..059f15bee42 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/MixTaxonomyRequest.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/data/MixTaxonomyMappingUpdateResponse.java @@ -21,17 +21,18 @@ import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +@Builder @Data @NoArgsConstructor @AllArgsConstructor -public class MixTaxonomyRequest implements Serializable { +public class MixTaxonomyMappingUpdateResponse implements Serializable { @Serial private static final long serialVersionUID = 1L; - private String identifier; - private String config; + private Long entityId; } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespace.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespace.java new file mode 100644 index 00000000000..d73813aeb3b --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespace.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.domain; + +import java.io.Serial; +import java.io.Serializable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("mix_xbrl_namespace") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public class MixReportXBRLNamespace implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Column("id") + private Long id; + @Column("prefix") + private String prefix; + @Column("url") + private String url; +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespaceRepository.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespaceRepository.java new file mode 100644 index 00000000000..a8806fa0464 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixReportXBRLNamespaceRepository.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.domain; + +import java.util.Optional; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +public interface MixReportXBRLNamespaceRepository + extends ListCrudRepository, QueryByExampleExecutor { + + Optional findOneByPrefix(String prefix); +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomy.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomy.java new file mode 100644 index 00000000000..79b3462a3f4 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomy.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.domain; + +import java.io.Serial; +import java.io.Serializable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("mix_taxonomy") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public final class MixTaxonomy implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Column("id") + private Long id; + + @Column("name") + private String name; + + @Column("namespace_id") + private Long namespaceId; + + @Column("dimension") + private String dimension; + + @Column("type") + private Integer type; + + @Column("description") + private String description; + + // TODO: this is never used, but creates an error on MySQL (tinyint vs boolean mapping) + // @Column("need_mapping") + // private Boolean needMapping; +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java new file mode 100644 index 00000000000..30e4abc7cf5 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.domain; + +import java.io.Serial; +import java.io.Serializable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +@Table("mix_taxonomy_mapping") +@Getter +@Setter +@NoArgsConstructor +@Accessors(chain = true) +public final class MixTaxonomyMapping implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Column("id") + private Long id; + + @Column("identifier") + private String identifier; + + @Column("config") + private String config; + + @Column("currency") + private String currency; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java similarity index 79% rename from fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java index 44df787b4ae..35baecaab26 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMappingRepository.java @@ -18,10 +18,8 @@ */ package org.apache.fineract.mix.domain; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; public interface MixTaxonomyMappingRepository - extends JpaRepository, JpaSpecificationExecutor { - -} + extends ListCrudRepository, QueryByExampleExecutor {} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyRepository.java b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyRepository.java new file mode 100644 index 00000000000..8b1fa29ad01 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyRepository.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.domain; + +import java.util.List; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +public interface MixTaxonomyRepository extends ListCrudRepository, QueryByExampleExecutor { + + List findAllByOrderByIdAsc(); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java b/fineract-mix/src/main/java/org/apache/fineract/mix/exception/MixReportXBRLMappingInvalidException.java similarity index 86% rename from fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/exception/MixReportXBRLMappingInvalidException.java index 45390617db7..cf78409424f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/exception/XBRLMappingInvalidException.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/exception/MixReportXBRLMappingInvalidException.java @@ -20,9 +20,9 @@ import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; -public class XBRLMappingInvalidException extends AbstractPlatformDomainRuleException { +public class MixReportXBRLMappingInvalidException extends AbstractPlatformDomainRuleException { - public XBRLMappingInvalidException(final String msg) { + public MixReportXBRLMappingInvalidException(final String msg) { super("error.msg.xbrl.report.mapping.invalid.id", "Mapping does not exist", msg); } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/handler/MixTaxonomyMappingUpdateCommandHandler.java b/fineract-mix/src/main/java/org/apache/fineract/mix/handler/MixTaxonomyMappingUpdateCommandHandler.java new file mode 100644 index 00000000000..af59efdd713 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/handler/MixTaxonomyMappingUpdateCommandHandler.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.handler; + +import io.github.resilience4j.retry.annotation.Retry; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateResponse; +import org.apache.fineract.mix.service.MixTaxonomyMappingWriteService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MixTaxonomyMappingUpdateCommandHandler + implements CommandHandler { + + private final MixTaxonomyMappingWriteService writeTaxonomyService; + + @Retry(name = "commandMixTaxonomyMappingUpdate", fallbackMethod = "fallback") + @Transactional + @Override + public MixTaxonomyMappingUpdateResponse handle(Command command) { + return writeTaxonomyService.updateMapping(command.getPayload()); + } + + @Override + public MixTaxonomyMappingUpdateResponse fallback(Command command, Throwable t) { + return CommandHandler.super.fallback(command, t); + } +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixReportXBRLNamespaceMapper.java b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixReportXBRLNamespaceMapper.java new file mode 100644 index 00000000000..bbd88503e78 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixReportXBRLNamespaceMapper.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.mapping; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.mix.data.MixReportXBRLNamespaceData; +import org.apache.fineract.mix.domain.MixReportXBRLNamespace; +import org.mapstruct.Mapper; + +@Mapper(config = MapstructMapperConfig.class) +public interface MixReportXBRLNamespaceMapper { + + MixReportXBRLNamespaceData map(MixReportXBRLNamespace source); +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMapper.java b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMapper.java new file mode 100644 index 00000000000..ad23c6dd93d --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMapper.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.mapping; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.mix.data.MixTaxonomyData; +import org.apache.fineract.mix.domain.MixTaxonomy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(config = MapstructMapperConfig.class) +public interface MixTaxonomyMapper { + + @Mapping(ignore = true, target = "namespace") + MixTaxonomyData map(MixTaxonomy source); +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingMapper.java b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingMapper.java new file mode 100644 index 00000000000..ac61c5eb492 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingMapper.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.mapping; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.mix.data.MixTaxonomyMappingData; +import org.apache.fineract.mix.domain.MixTaxonomyMapping; +import org.mapstruct.Mapper; + +@Mapper(config = MapstructMapperConfig.class) +public interface MixTaxonomyMappingMapper { + + MixTaxonomyMappingData map(MixTaxonomyMapping source); +} diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingUpdateRequestMapper.java b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingUpdateRequestMapper.java new file mode 100644 index 00000000000..a985c30a625 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/mapping/MixTaxonomyMappingUpdateRequestMapper.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.mapping; + +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; +import org.apache.fineract.mix.domain.MixTaxonomyMapping; +import org.mapstruct.Mapper; + +@Mapper(config = MapstructMapperConfig.class) +public interface MixTaxonomyMappingUpdateRequestMapper { + + MixTaxonomyMapping map(MixTaxonomyMappingUpdateRequest source); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLBuilder.java similarity index 80% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLBuilder.java index 6af912acbc5..1d97f3210aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLBuilder.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLBuilder.java @@ -25,36 +25,40 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.fineract.mix.data.ContextData; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixReportXBRLContextData; +import org.apache.fineract.mix.data.MixReportXBRLData; +import org.apache.fineract.mix.data.MixReportXBRLNamespaceData; import org.apache.fineract.mix.data.MixTaxonomyData; -import org.apache.fineract.mix.data.NamespaceData; -import org.apache.fineract.mix.data.XBRLData; -import org.apache.fineract.mix.exception.XBRLMappingInvalidException; +import org.apache.fineract.mix.exception.MixReportXBRLMappingInvalidException; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +@Slf4j +@RequiredArgsConstructor @Component -public class XBRLBuilder { +public class MixReportXBRLBuilder { + // NOTE: see https://www.xbrl.org/taxonomyrecognition/mx_2009-06-19_summary-page.htm private static final String SCHEME_URL = "http://www.themix.org"; private static final String IDENTIFIER = "000000"; private static final String UNITID_PURE = "Unit1"; private static final String UNITID_CUR = "Unit2"; - @Autowired - private NamespaceReadPlatformService readNamespaceService; + private final MixReportXBRLNamespaceReadService readNamespaceService; - public String build(final XBRLData xbrlData) { + // TODO: we should do this with JAXB + public String build(final MixReportXBRLData xbrlData) { return this.build(xbrlData.getResultMap(), xbrlData.getStartDate(), xbrlData.getEndDate(), xbrlData.getCurrency()); } public String build(final Map map, final Date startDate, final Date endDate, final String currency) { Integer instantScenarioCounter = 0; Integer durationScenarioCounter = 0; - Map contextMap = new HashMap<>(); + Map contextMap = new HashMap<>(); final Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("xbrl"); @@ -79,17 +83,17 @@ public String build(final Map map, final Date start private Element addTaxonomy(final Element rootElement, final MixTaxonomyData taxonomy, final BigDecimal value, final Date startDate, final Date endDate, Integer instantScenarioCounter, Integer durationScenarioCounter, - final Map contextMap) { + final Map contextMap) { // throw an error is start / endate is null if (startDate == null || endDate == null) { - throw new XBRLMappingInvalidException("start date and end date should not be null"); + throw new MixReportXBRLMappingInvalidException("start date and end date should not be null"); } final String prefix = taxonomy.getNamespace(); String qname = taxonomy.getName(); if (prefix != null && !prefix.isEmpty()) { - final NamespaceData ns = this.readNamespaceService.retrieveNamespaceByPrefix(prefix); + final MixReportXBRLNamespaceData ns = this.readNamespaceService.retrieveNamespaceByPrefix(prefix); if (ns != null) { rootElement.addNamespace(prefix, ns.getUrl()); @@ -102,20 +106,20 @@ private Element addTaxonomy(final Element rootElement, final MixTaxonomyData tax final String dimension = taxonomy.getDimension(); final SimpleDateFormat timeFormat = new SimpleDateFormat("MM_dd_yyyy"); - ContextData context = null; + MixReportXBRLContextData context = null; if (dimension != null) { final List dims = Splitter.on(':').splitToList(dimension); if (dims.size() == 2) { - context = new ContextData().setDimensionType(dims.get(0)).setDimension(dims.get(1)).setPeriodType( - taxonomy.getType().equals(MixTaxonomyData.BALANCESHEET) || taxonomy.getType().equals(MixTaxonomyData.PORTFOLIO) ? 0 + context = new MixReportXBRLContextData().setDimensionType(dims.get(0)).setDimension(dims.get(1)).setPeriodType( + taxonomy.getType().equals(MixTaxonomyData.BALANCE_SHEET) || taxonomy.getType().equals(MixTaxonomyData.PORTFOLIO) ? 0 : 1); } } if (context == null) { - context = new ContextData().setPeriodType( - taxonomy.getType().equals(MixTaxonomyData.BALANCESHEET) || taxonomy.getType().equals(MixTaxonomyData.PORTFOLIO) ? 0 + context = new MixReportXBRLContextData().setPeriodType( + taxonomy.getType().equals(MixTaxonomyData.BALANCE_SHEET) || taxonomy.getType().equals(MixTaxonomyData.PORTFOLIO) ? 0 : 1); } @@ -169,10 +173,11 @@ private void addCurrencyUnit(final Element root, final String currencyCode) { } - private void addContexts(final Element root, final Date startDate, final Date endDate, final Map contextMap) { + private void addContexts(final Element root, final Date startDate, final Date endDate, + final Map contextMap) { final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - for (final Map.Entry entry : contextMap.entrySet()) { - final ContextData context = entry.getKey(); + for (final Map.Entry entry : contextMap.entrySet()) { + final MixReportXBRLContextData context = entry.getKey(); final Element contextElement = root.addElement("context"); contextElement.addAttribute("id", entry.getValue()); contextElement.addElement("entity").addElement("identifier").addAttribute("scheme", SCHEME_URL).addText(IDENTIFIER); diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadService.java similarity index 80% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadService.java index aa5175406e8..8296fc6c164 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformService.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadService.java @@ -18,11 +18,9 @@ */ package org.apache.fineract.mix.service; -import org.apache.fineract.mix.data.NamespaceData; +import org.apache.fineract.mix.data.MixReportXBRLNamespaceData; -public interface NamespaceReadPlatformService { +public interface MixReportXBRLNamespaceReadService { - NamespaceData retrieveNamespaceById(Long id); - - NamespaceData retrieveNamespaceByPrefix(String prefix); + MixReportXBRLNamespaceData retrieveNamespaceByPrefix(String prefix); } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadServiceImpl.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadServiceImpl.java new file mode 100644 index 00000000000..7a4c1f998e8 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLNamespaceReadServiceImpl.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixReportXBRLNamespaceData; +import org.apache.fineract.mix.domain.MixReportXBRLNamespaceRepository; +import org.apache.fineract.mix.mapping.MixReportXBRLNamespaceMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class MixReportXBRLNamespaceReadServiceImpl implements MixReportXBRLNamespaceReadService { + + private final MixReportXBRLNamespaceRepository repository; + private final MixReportXBRLNamespaceMapper mapper; + + @Override + public MixReportXBRLNamespaceData retrieveNamespaceByPrefix(final String prefix) { + return repository.findOneByPrefix(prefix).map(mapper::map).orElse(null); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultService.java similarity index 82% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultService.java index 585b3e46c47..9e4fb0f4624 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultService.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultService.java @@ -19,10 +19,10 @@ package org.apache.fineract.mix.service; import java.sql.Date; -import org.apache.fineract.mix.data.XBRLData; +import org.apache.fineract.mix.data.MixReportXBRLData; -public interface XBRLResultService { +public interface MixReportXBRLResultService { - XBRLData getXBRLResult(Date startDate, Date endDate, String currency); + MixReportXBRLData getXBRLResult(Date startDate, Date endDate, String currency); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultServiceImpl.java similarity index 56% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultServiceImpl.java index 0628a4f6a89..17eb95cc868 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/XBRLResultServiceImpl.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixReportXBRLResultServiceImpl.java @@ -31,45 +31,47 @@ import javax.script.ScriptEngineManager; import javax.script.ScriptException; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixReportXBRLData; import org.apache.fineract.mix.data.MixTaxonomyData; import org.apache.fineract.mix.data.MixTaxonomyMappingData; -import org.apache.fineract.mix.data.XBRLData; -import org.apache.fineract.mix.exception.XBRLMappingInvalidException; +import org.apache.fineract.mix.exception.MixReportXBRLMappingInvalidException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Component; -@Component @Slf4j -public class XBRLResultServiceImpl implements XBRLResultService { +@Component +public class MixReportXBRLResultServiceImpl implements MixReportXBRLResultService { private static final ScriptEngine SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName("JavaScript"); - private final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService; - private final MixTaxonomyReadPlatformService readTaxonomyService; + private final MixTaxonomyMappingReadService readTaxonomyMappingService; + private final MixTaxonomyReadService readTaxonomyService; private final JdbcTemplate jdbcTemplate; @Autowired - public XBRLResultServiceImpl(final JdbcTemplate jdbcTemplate, final MixTaxonomyMappingReadPlatformService readTaxonomyMappingService, - final MixTaxonomyReadPlatformService readTaxonomyService) { + public MixReportXBRLResultServiceImpl(final JdbcTemplate jdbcTemplate, final MixTaxonomyMappingReadService readTaxonomyMappingService, + final MixTaxonomyReadService readTaxonomyService) { this.readTaxonomyMappingService = readTaxonomyMappingService; this.readTaxonomyService = readTaxonomyService; this.jdbcTemplate = jdbcTemplate; } @Override - public XBRLData getXBRLResult(final Date startDate, final Date endDate, final String currency) { + public MixReportXBRLData getXBRLResult(final Date startDate, final Date endDate, final String currency) { - final HashMap config = retrieveTaxonomyConfig(startDate, endDate); - if (config == null || config.size() == 0) { - throw new XBRLMappingInvalidException("Mapping is empty"); + final Map config = retrieveTaxonomyConfig(startDate, endDate); + + if (config == null || config.isEmpty()) { + throw new MixReportXBRLMappingInvalidException("Mapping is empty"); } - return new XBRLData().setResultMap(config).setStartDate(startDate).setEndDate(endDate).setCurrency(currency); + + return new MixReportXBRLData().setResultMap(config).setStartDate(startDate).setEndDate(endDate).setCurrency(currency); } @SuppressWarnings("unchecked") - private HashMap retrieveTaxonomyConfig(final Date startDate, final Date endDate) { + private Map retrieveTaxonomyConfig(final Date startDate, final Date endDate) { final MixTaxonomyMappingData taxonomyMapping = this.readTaxonomyMappingService.retrieveTaxonomyMapping(); if (taxonomyMapping == null) { return null; @@ -77,7 +79,7 @@ private HashMap retrieveTaxonomyConfig(final Date s final String config = taxonomyMapping.getConfig(); if (config != null) { // - HashMap configMap = new HashMap<>(); + Map configMap = new HashMap<>(); configMap = new Gson().fromJson(config, configMap.getClass()); if (configMap == null) { return null; @@ -96,41 +98,34 @@ private HashMap retrieveTaxonomyConfig(final Date s return null; } + // TODO: this should at least use prepared statements and not just string concatenate the date objects! private String getAccountSql(final Date startDate, final Date endDate) { - return "select debits.glcode as 'glcode', debits.name as 'name', coalesce(debits.debitamount,0)-coalesce(credits.creditamount,0)) as 'balance' " - + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' " - + "from acc_gl_journal_entry,acc_gl_account " + "where acc_gl_account.id = acc_gl_journal_entry.account_id " - + "and acc_gl_journal_entry.type_enum=2 " + "and acc_gl_journal_entry.entry_date <= " + endDate - + " and acc_gl_journal_entry.entry_date > " + startDate - // "and (acc_gl_journal_entry.office_id=${branch} or - // ${branch}=1) " - // + - + " group by glcode " + "order by glcode) debits " + "LEFT OUTER JOIN " - + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' " - + "from acc_gl_journal_entry,acc_gl_account " + "where acc_gl_account.id = acc_gl_journal_entry.account_id " - + "and acc_gl_journal_entry.type_enum=1 " + "and acc_gl_journal_entry.entry_date <= " + endDate - + " and acc_gl_journal_entry.entry_date > " + startDate - // "and (acc_gl_journal_entry.office_id=${branch} or - // ${branch}=1) " - // + - + " group by glcode " + "order by glcode) credits " + "on debits.glcode=credits.glcode " + "union " - + "select credits.glcode as 'glcode', credits.name as 'name', coalesce(debits.debitamount,0)-coalesce(credits.creditamount,0)) as 'balance' " - + "from (select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'debitamount' " - + "from acc_gl_journal_entry,acc_gl_account " + "where acc_gl_account.id = acc_gl_journal_entry.account_id " - + "and acc_gl_journal_entry.type_enum=2 " + "and acc_gl_journal_entry.entry_date <= " + endDate - + " and acc_gl_journal_entry.entry_date > " + startDate - // "and (acc_gl_journal_entry.office_id=${branch} or - // ${branch}=1) " - // + - + " group by glcode " + "order by glcode) debits " + "RIGHT OUTER JOIN " - + "(select acc_gl_account.gl_code as 'glcode',name,sum(amount) as 'creditamount' " - + "from acc_gl_journal_entry,acc_gl_account " + "where acc_gl_account.id = acc_gl_journal_entry.account_id " - + "and acc_gl_journal_entry.type_enum=1 " + "and acc_gl_journal_entry.entry_date <= " + endDate - + " and acc_gl_journal_entry.entry_date > " + startDate - // "and (acc_gl_journal_entry.office_id=${branch} or - // ${branch}=1) " - // + - + " group by name, glcode " + "order by glcode) credits " + "on debits.glcode=credits.glcode;"; + return "SELECT debits.glcode AS 'glcode', debits.name AS 'name', COALESCE(debits.debitamount,0)-COALESCE(credits.creditamount,0)) AS 'balance' " + + "FROM (SELECT acc_gl_account.gl_code AS 'glcode',name,SUM(amount) AS 'debitamount' " + + "FROM acc_gl_journal_entry,acc_gl_account WHERE acc_gl_account.id = acc_gl_journal_entry.account_id " + + "AND acc_gl_journal_entry.type_enum=2 AND acc_gl_journal_entry.entry_date <= " + endDate + + " AND acc_gl_journal_entry.entry_date > " + startDate + // + + " GROUP BY glcode ORDER BY glcode) debits LEFT OUTER JOIN " + + "(SELECT acc_gl_account.gl_code AS 'glcode',name,SUM(amount) AS 'creditamount' " + + "FROM acc_gl_journal_entry,acc_gl_account WHERE acc_gl_account.id = acc_gl_journal_entry.account_id " + + "AND acc_gl_journal_entry.type_enum=1 AND acc_gl_journal_entry.entry_date <= " + endDate + + " AND acc_gl_journal_entry.entry_date > " + startDate + // + + " GROUP BY glcode ORDER BY glcode) credits ON debits.glcode=credits.glcode UNION " + + "SELECT credits.glcode AS 'glcode', credits.name AS 'name', COALESCE(debits.debitamount,0)-COALESCE(credits.creditamount,0)) AS 'balance' " + + "FROM (SELECT acc_gl_account.gl_code AS 'glcode',name,SUM(amount) AS 'debitamount' " + + "FROM acc_gl_journal_entry,acc_gl_account WHERE acc_gl_account.id = acc_gl_journal_entry.account_id " + + "AND acc_gl_journal_entry.type_enum=2 AND acc_gl_journal_entry.entry_date <= " + endDate + + " AND acc_gl_journal_entry.entry_date > " + startDate + // + + " GROUP BY glcode ORDER BY glcode) debits RIGHT OUTER JOIN " + + "(SELECT acc_gl_account.gl_code AS 'glcode',name,SUM(amount) AS 'creditamount' " + + "FROM acc_gl_journal_entry,acc_gl_account WHERE acc_gl_account.id = acc_gl_journal_entry.account_id " + + "AND acc_gl_journal_entry.type_enum=1 AND acc_gl_journal_entry.entry_date <= " + endDate + + " AND acc_gl_journal_entry.entry_date > " + startDate + // + + " GROUP BY name, glcode ORDER BY glcode) credits ON debits.glcode=credits.glcode;"; } private Map setupBalanceMap(final String sql) { @@ -156,6 +151,7 @@ private BigDecimal processMappingString(Map accountBalanceMa // evaluate the expression float eval = 0f; try { + // TODO: this doesn't work anymore in modern JVMs!!!! final Number value = (Number) SCRIPT_ENGINE.eval(mappingString); if (value != null) { eval = value.floatValue(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadService.java similarity index 94% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadService.java index 38bbf8f5b1a..32c0f6c8fb5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformService.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadService.java @@ -20,7 +20,7 @@ import org.apache.fineract.mix.data.MixTaxonomyMappingData; -public interface MixTaxonomyMappingReadPlatformService { +public interface MixTaxonomyMappingReadService { MixTaxonomyMappingData retrieveTaxonomyMapping(); } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadServiceImpl.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadServiceImpl.java new file mode 100644 index 00000000000..4047f1d09ef --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadServiceImpl.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixTaxonomyMappingData; +import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; +import org.apache.fineract.mix.mapping.MixTaxonomyMappingMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class MixTaxonomyMappingReadServiceImpl implements MixTaxonomyMappingReadService { + + private final MixTaxonomyMappingRepository repository; + private final MixTaxonomyMappingMapper mapper; + + @Override + public MixTaxonomyMappingData retrieveTaxonomyMapping() { + return repository.findAll().stream().findFirst().map(mapper::map).orElse(null); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteService.java similarity index 75% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteService.java index 77fa2929a83..1c91559b6bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformService.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteService.java @@ -18,10 +18,10 @@ */ package org.apache.fineract.mix.service; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateResponse; -public interface MixTaxonomyMappingWritePlatformService { +public interface MixTaxonomyMappingWriteService { - CommandProcessingResult updateMapping(Long mappingId, JsonCommand command); + MixTaxonomyMappingUpdateResponse updateMapping(MixTaxonomyMappingUpdateRequest request); } diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteServiceImpl.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteServiceImpl.java new file mode 100644 index 00000000000..4876476fa62 --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWriteServiceImpl.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.service; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateRequest; +import org.apache.fineract.mix.data.MixTaxonomyMappingUpdateResponse; +import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; +import org.apache.fineract.mix.mapping.MixTaxonomyMappingUpdateRequestMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@RequiredArgsConstructor +@Service +public class MixTaxonomyMappingWriteServiceImpl implements MixTaxonomyMappingWriteService { + + private final MixTaxonomyMappingRepository repository; + private final MixTaxonomyMappingUpdateRequestMapper mapper; + + @Transactional + @Override + public MixTaxonomyMappingUpdateResponse updateMapping(@Valid final MixTaxonomyMappingUpdateRequest request) { + final var taxonomyMapping = mapper.map(request); + + repository.save(taxonomyMapping); + + return MixTaxonomyMappingUpdateResponse.builder().entityId(taxonomyMapping.getId()).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadService.java similarity index 95% rename from fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java rename to fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadService.java index dd4525e57ec..e541366c3a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformService.java +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadService.java @@ -21,7 +21,7 @@ import java.util.List; import org.apache.fineract.mix.data.MixTaxonomyData; -public interface MixTaxonomyReadPlatformService { +public interface MixTaxonomyReadService { List retrieveAll(); diff --git a/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadServiceImpl.java b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadServiceImpl.java new file mode 100644 index 00000000000..152055967fb --- /dev/null +++ b/fineract-mix/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadServiceImpl.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.mix.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.mix.data.MixTaxonomyData; +import org.apache.fineract.mix.domain.MixTaxonomyRepository; +import org.apache.fineract.mix.mapping.MixTaxonomyMapper; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class MixTaxonomyReadServiceImpl implements MixTaxonomyReadService { + + private final MixTaxonomyRepository repository; + private final MixTaxonomyMapper mapper; + + @Override + public List retrieveAll() { + return repository.findAllByOrderByIdAsc().stream().map(mapper::map).toList(); + } + + @Override + public MixTaxonomyData retrieveOne(final Long id) { + return repository.findById(id).map(mapper::map).orElse(null); + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index ea0aeedb42f..3bbb4b43abc 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -63,6 +63,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; @@ -2917,6 +2918,7 @@ private Set getLoanChargesOfInstallment(final Set charge private Money processPeriodsVertically(LoanTransaction loanTransaction, TransactionCtx ctx, Money transactionAmountUnprocessed, LoanPaymentAllocationRule paymentAllocationRule, List transactionMappings, Balances balances) { + Loan loan = loanTransaction.getLoan(); VerticalPaymentAllocationContext paymentAllocationContext = new VerticalPaymentAllocationContext(ctx, loanTransaction, paymentAllocationRule.getFutureInstallmentAllocationRule(), transactionMappings, balances); paymentAllocationContext.setTransactionAmountUnprocessed(transactionAmountUnprocessed); @@ -2924,6 +2926,18 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact paymentAllocationContext.setAllocatedAmount(Money.zero(ctx.getCurrency())); paymentAllocationContext.setInstallment(null); paymentAllocationContext.setPaymentAllocationType(paymentAllocationType); + if (isInterestRecalculationSupported(ctx, loanTransaction.getLoan())) { + // Clear any previously skipped installments before re-evaluating + ProgressiveTransactionCtx progressiveTransactionCtx = (ProgressiveTransactionCtx) ctx; + progressiveTransactionCtx.getSkipRepaymentScheduleInstallments().clear(); + paymentAllocationContext + .setInAdvanceInstallmentsFilteringRules(installment -> loanTransaction.isBefore(installment.getDueDate()) + && installment.isOutstandingBalanceNotZero(paymentAllocationType.getAllocationType(), ctx.getCurrency()) + && !progressiveTransactionCtx.getSkipRepaymentScheduleInstallments().contains(installment)); + } else { + paymentAllocationContext.setInAdvanceInstallmentsFilteringRules(getFilterPredicate( + paymentAllocationContext.getPaymentAllocationType(), paymentAllocationContext.getCtx().getCurrency())); + } LoopGuard.runSafeDoWhileLoop(paymentAllocationContext.getCtx().getInstallments().size() * 100, // paymentAllocationContext, // (VerticalPaymentAllocationContext context) -> context.getInstallment() != null @@ -2944,11 +2958,20 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( context.getTransactionMappings(), context.getLoanTransaction(), context.getInstallment(), context.getCtx().getCurrency()); - context.setAllocatedAmount( - processPaymentAllocation(context.getPaymentAllocationType(), context.getInstallment(), - context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), - loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges, - context.getBalances(), LoanRepaymentScheduleInstallment.PaymentAction.PAY)); + + if (isInterestRecalculationSupported(context.getCtx(), loan)) { + context.setAllocatedAmount(handlingPaymentAllocationForInterestBearingProgressiveLoan( + context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), + context.getBalances(), paymentAllocationType, context.getInstallment(), + (ProgressiveTransactionCtx) context.getCtx(), loanTransactionToRepaymentScheduleMapping, + oldestPastDueInstallmentCharges)); + } else { + context.setAllocatedAmount( + processPaymentAllocation(context.getPaymentAllocationType(), context.getInstallment(), + context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), + loanTransactionToRepaymentScheduleMapping, oldestPastDueInstallmentCharges, + context.getBalances(), LoanRepaymentScheduleInstallment.PaymentAction.PAY)); + } context.setTransactionAmountUnprocessed( context.getTransactionAmountUnprocessed().minus(context.getAllocatedAmount())); } @@ -2963,11 +2986,19 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( context.getTransactionMappings(), context.getLoanTransaction(), context.getInstallment(), context.getCtx().getCurrency()); - context.setAllocatedAmount( - processPaymentAllocation(context.getPaymentAllocationType(), context.getInstallment(), - context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), - loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges, context.getBalances(), - LoanRepaymentScheduleInstallment.PaymentAction.PAY)); + if (isInterestRecalculationSupported(context.getCtx(), loan)) { + context.setAllocatedAmount(handlingPaymentAllocationForInterestBearingProgressiveLoan( + context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), + context.getBalances(), paymentAllocationType, context.getInstallment(), + (ProgressiveTransactionCtx) context.getCtx(), loanTransactionToRepaymentScheduleMapping, + dueInstallmentCharges)); + } else { + context.setAllocatedAmount( + processPaymentAllocation(context.getPaymentAllocationType(), context.getInstallment(), + context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), + loanTransactionToRepaymentScheduleMapping, dueInstallmentCharges, + context.getBalances(), LoanRepaymentScheduleInstallment.PaymentAction.PAY)); + } context.setTransactionAmountUnprocessed( context.getTransactionAmountUnprocessed().minus(context.getAllocatedAmount())); } @@ -2979,17 +3010,20 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact // element. List currentInstallments = new ArrayList<>(); if (FutureInstallmentAllocationRule.REAMORTIZATION.equals(context.getFutureInstallmentAllocationRule())) { - currentInstallments = context.getCtx().getInstallments().stream().filter(predicate) + currentInstallments = context.getCtx().getInstallments().stream() + .filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules) .filter(e -> context.getLoanTransaction().isBefore(e.getDueDate())).toList(); } else if (FutureInstallmentAllocationRule.NEXT_INSTALLMENT .equals(context.getFutureInstallmentAllocationRule())) { - currentInstallments = context.getCtx().getInstallments().stream().filter(predicate) + currentInstallments = context.getCtx().getInstallments().stream() + .filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules) .filter(e -> context.getLoanTransaction().isBefore(e.getDueDate())) .min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream() .toList(); } else if (FutureInstallmentAllocationRule.LAST_INSTALLMENT .equals(context.getFutureInstallmentAllocationRule())) { - currentInstallments = context.getCtx().getInstallments().stream().filter(predicate) + currentInstallments = context.getCtx().getInstallments().stream() + .filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules) .filter(e -> context.getLoanTransaction().isBefore(e.getDueDate())) .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream() .toList(); @@ -2998,14 +3032,16 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact // get current installment where from date < transaction date < to date OR // transaction date // is on first installment's first day ( from day ) - currentInstallments = context.getCtx().getInstallments().stream().filter(predicate) + currentInstallments = context.getCtx().getInstallments().stream() + .filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules) .filter(e -> context.getLoanTransaction().isBefore(e.getDueDate())) .filter(f -> context.getLoanTransaction().isAfter(f.getFromDate()) || context.getLoanTransaction().isOn(f.getFromDate())) .toList(); // if there is no current in advance installment resolve similar to LAST_INSTALLMENT if (currentInstallments.isEmpty()) { - currentInstallments = context.getCtx().getInstallments().stream().filter(predicate) + currentInstallments = context.getCtx().getInstallments().stream() + .filter(paymentAllocationContext.inAdvanceInstallmentsFilteringRules) .filter(e -> context.getLoanTransaction().isBefore(e.getDueDate())) .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream() .toList(); @@ -3034,18 +3070,37 @@ private Money processPeriodsVertically(LoanTransaction loanTransaction, Transact LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping( context.getTransactionMappings(), context.getLoanTransaction(), context.getInstallment(), context.getCtx().getCurrency()); - Money internalPaidPortion = processPaymentAllocation(context.getPaymentAllocationType(), - context.getInstallment(), context.getLoanTransaction(), evenPortion, - loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, - context.getBalances(), LoanRepaymentScheduleInstallment.PaymentAction.PAY); - // Some extra logic to allocate as much as possible across the installments if - // the - // outstanding balances are different - if (internalPaidPortion.isGreaterThanZero()) { - context.setAllocatedAmount(internalPaidPortion); + if (isInterestRecalculationSupported(context.getCtx(), loan)) { + Money internalPaidPortion = handlingPaymentAllocationForInterestBearingProgressiveLoan( + context.getLoanTransaction(), context.getTransactionAmountUnprocessed(), + context.getBalances(), paymentAllocationType, context.getInstallment(), + (ProgressiveTransactionCtx) context.getCtx(), loanTransactionToRepaymentScheduleMapping, + inAdvanceInstallmentCharges); + // Some extra logic to allocate as much as possible across the installments + // if + // the + // outstanding balances are different + if (internalPaidPortion.isGreaterThanZero()) { + context.setAllocatedAmount(internalPaidPortion); + } + context.setTransactionAmountUnprocessed( + context.getTransactionAmountUnprocessed().minus(internalPaidPortion)); + } else { + Money internalPaidPortion = processPaymentAllocation(context.getPaymentAllocationType(), + context.getInstallment(), context.getLoanTransaction(), evenPortion, + loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, + context.getBalances(), LoanRepaymentScheduleInstallment.PaymentAction.PAY); + + // Some extra logic to allocate as much as possible across the installments + // if + // the + // outstanding balances are different + if (internalPaidPortion.isGreaterThanZero()) { + context.setAllocatedAmount(internalPaidPortion); + } + context.setTransactionAmountUnprocessed( + context.getTransactionAmountUnprocessed().minus(internalPaidPortion)); } - context.setTransactionAmountUnprocessed( - context.getTransactionAmountUnprocessed().minus(internalPaidPortion)); } } else { context.setInstallment(null); @@ -3207,7 +3262,14 @@ private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) { } } } else { - handleReAgeWithCommonStrategy(loanTransaction, new CommonReAgeSettings(), ctx); + CommonReAgeSettings settings = switch (loanReAgeParameter.getInterestHandlingType()) { + case LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST -> new CommonReAgeSettings(false, true, true, true); + case LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST -> new CommonReAgeSettings(true, true, true, true); + case LoanReAgeInterestHandlingType.DEFAULT -> new CommonReAgeSettings(); + case null -> new CommonReAgeSettings(); + default -> throw new NotImplementedException(); + }; + handleReAgeWithCommonStrategy(loanTransaction, settings, ctx); } if (loanTransaction.getAmount().compareTo(ZERO) == 0) { loanTransaction.reverse(); @@ -3569,6 +3631,7 @@ private static class VerticalPaymentAllocationContext implements LoopContext { private Money transactionAmountUnprocessed; private Money allocatedAmount; private PaymentAllocationType paymentAllocationType; + private Predicate inAdvanceInstallmentsFilteringRules; VerticalPaymentAllocationContext(TransactionCtx ctx, LoanTransaction loanTransaction, FutureInstallmentAllocationRule futureInstallmentAllocationRule, diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java index 487c7069ed1..0254a6a51a7 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanConfigurationDetailsMapper.java @@ -57,7 +57,7 @@ public static ILoanConfigurationDetails map(Loan loan) { loanProductRelatedDetail.getNumberOfRepayments(), loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate(), loanProductRelatedDetail.getDaysInYearCustomStrategy(), loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation(), loan.isInterestRecalculationEnabled(), getRestFrequencyType(loan), getPreCloseInterestCalculationStrategy(loan), - loan.isAllowFullTermForTranche()); + loan.isAllowFullTermForTranche(), loan.getLoanProductRelatedDetail().getLoanScheduleProcessingType()); } private static RecalculationFrequencyType getRestFrequencyType(Loan loan) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index a39748da338..77c9a48a5d1 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -54,6 +54,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator; import org.apache.fineract.portfolio.loanproduct.calc.data.EmiAdjustment; import org.apache.fineract.portfolio.loanproduct.calc.data.EmiChangeOperation; @@ -368,6 +369,18 @@ public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleMo }); } + public void addOverdueBalanceCorrection(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate correctionDate, + final Money overdueAmount) { + scheduleModel + .changeOutstandingBalanceAndUpdateInterestPeriods(correctionDate, scheduleModel.zero(), overdueAmount, scheduleModel.zero()) + .ifPresent(repaymentPeriod -> { + scheduleModel.recordOverdueCorrection(correctionDate, overdueAmount, repaymentPeriod.getDueDate()); + calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel); + calculateOutstandingBalance(scheduleModel); + calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, correctionDate); + }); + } + @Override public void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodFromDate, LocalDate repaymentPeriodDueDate, LocalDate transactionDate, Money interestAmount) { @@ -512,10 +525,15 @@ public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleMo false)); // } } - + Money duePrincipal = repaymentPeriod.getDuePrincipal(); + Money dueInterest = repaymentPeriod.getDueInterest(); + if (scheduleModel.loanProductRelatedDetail().getLoanScheduleProcessingType() == LoanScheduleProcessingType.VERTICAL + && notFullyRepaidRepaymentPeriodCount > 1) { + duePrincipal = repaymentPeriod.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(); + } return new PeriodDueDetails(repaymentPeriod.getEmi(), // - repaymentPeriod.getDuePrincipal(), // - repaymentPeriod.getDueInterest()); // + duePrincipal, // + dueInterest); // } @Override @@ -700,6 +718,8 @@ public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveLoanInterest moveOutstandingAmountsFromPeriodsBeforeTransactionDate(scheduleModel.repaymentPeriods(), targetDate); + collapseIntermediateStubPeriods(scheduleModel); + final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generateTemporaryScheduleModel(loanApplicationTerms, mc, reAgePeriodStartDate, reAgePeriodStartDate); @@ -884,7 +904,7 @@ private void moveOutstandingAmountsFromPeriodsBeforeTransactionDateForEqualInter } rp.setEmi(rp.getTotalPaidAmount()); rp.moveOutstandingDueToReAging(); - rp.setNoUnrecognisedInterest(true); + rp.setInterestMovedDownward(true); }); } @@ -919,9 +939,9 @@ private boolean adjustOverduePrincipal(final LocalDate currentDate, final Repaym if (!currentDate.equals(model.lastOverdueBalanceChange())) { if (model.lastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(model.lastOverdueBalanceChange())) { - addBalanceCorrection(model, fromDate, overduePrincipal); + addOverdueBalanceCorrection(model, fromDate, overduePrincipal); } else { - addBalanceCorrection(model, model.lastOverdueBalanceChange(), overduePrincipal); + addOverdueBalanceCorrection(model, model.lastOverdueBalanceChange(), overduePrincipal); } if (currentDate.isAfter(fromDate) && !currentDate.isAfter(toDate)) { @@ -931,7 +951,7 @@ private boolean adjustOverduePrincipal(final LocalDate currentDate, final Repaym } else { lastOverdueBalanceChange = currentDate; } - addBalanceCorrection(model, lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated()); + addOverdueBalanceCorrection(model, lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated()); model.lastOverdueBalanceChange(lastOverdueBalanceChange); } return true; @@ -1064,6 +1084,31 @@ private static void moveOutstandingAmountsFromPeriodsBeforeTransactionDate(final }); } + private void collapseIntermediateStubPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel) { + final List periods = scheduleModel.repaymentPeriods(); + if (periods.size() <= 1) { + return; + } + // Only collapse if ALL periods are zero-EMI stubs (no principal due, no interest due, no paid amounts). + // This handles the repeated re-aging case where each re-age leaves behind a 1-day stub period, + // without affecting legitimate paid installments in multi-disbursement scenarios. + final boolean allPeriodsAreStubs = periods.stream() + .allMatch(rp -> rp.getEmi().isZero() && rp.getDuePrincipal().isZero() && rp.getDueInterest().isZero()); + if (!allPeriodsAreStubs) { + return; + } + final RepaymentPeriod firstPeriod = periods.getFirst(); + final RepaymentPeriod lastPeriod = periods.getLast(); + final LocalDate lastDueDate = lastPeriod.getDueDate(); + + firstPeriod.setDueDate(lastDueDate); + firstPeriod.getInterestPeriods().getLast().setDueDate(lastDueDate); + + periods.subList(1, periods.size()).clear(); + + calculateRateFactorForRepaymentPeriod(firstPeriod, scheduleModel); + } + private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate tillDate) { Money totalDuePaidDiff = scheduleModel.getTotalDuePrincipal().minus(scheduleModel.getTotalPaidPrincipal()); @@ -1086,7 +1131,7 @@ private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestSchedu findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> { repaymentPeriod.setFutureUnrecognizedInterest(scheduleModel.zero()); scheduleModel.repaymentPeriods().forEach(rp -> { - rp.setInterestMoved(false); + rp.setInterestMovedUpward(false); }); MathContext mc = scheduleModel.mc(); @@ -1125,14 +1170,31 @@ private void calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(Pr RepaymentPeriod repaymentPeriod, LocalDate tillDate) { MathContext mc = scheduleModel.mc(); ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc); + + // Reverse overdue corrections on the copy when they exist ON the target period + // but NOT beyond it. Beyond-target corrections provide legitimate cascading interest. + // Without reversal, overdue-inflated IPs create phantom futureUnrecognizedInterest. + final boolean hasOverdueCorrectionsBeyondTarget = scheduleModelCopy.hasOverdueCorrectionsBeyondDate(repaymentPeriod.getDueDate()); + final boolean hasOverdueCorrectionsOnTarget = scheduleModelCopy.hasOverdueCorrectionsOnDate(repaymentPeriod.getDueDate()); + final boolean shouldResetOverdue = !hasOverdueCorrectionsBeyondTarget && hasOverdueCorrectionsOnTarget; + + if (shouldResetOverdue) { + scheduleModelCopy.reverseOverdueCorrections(); + } + calculateRateFactorForScheduleTillDateInclusive(scheduleModelCopy, tillDate); + + if (shouldResetOverdue) { + calculateOutstandingBalance(scheduleModelCopy); + } + Optional futureUnrecognizedInterestPeriod = getPeriodWithUnrecognizedInterest(repaymentPeriod, scheduleModelCopy); futureUnrecognizedInterestPeriod.ifPresent(period -> { repaymentPeriod.setFutureUnrecognizedInterest(period.getUnrecognizedInterest()); scheduleModel.repaymentPeriods().stream().filter(rp -> rp.getDueDate().isAfter(repaymentPeriod.getDueDate())) // .forEach(rp -> { - rp.setInterestMoved(true); + rp.setInterestMovedUpward(true); }); }); } @@ -1501,7 +1563,7 @@ private void calculateEMIOnActualModelWithFlatInterestMethod(List { @@ -1687,6 +1750,14 @@ private BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final Bi return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc); } + /** + * Calculate the EMI (Equal Monthly Installment) value for fixed interest portion + */ + private BigDecimal calculateEMIValueForFixedInterest(final List repaymentPeriods, MathContext mc) { + return repaymentPeriods.stream().map(RepaymentPeriod::getFixedInterest).map(Money::getAmount).reduce(ZERO, BigDecimal::add) + .divide(BigDecimal.valueOf(repaymentPeriods.isEmpty() ? 1 : repaymentPeriods.size()), mc); + } + /** * To calculate the daily payment, we first need to calculate something called the Rate Factor. We're going to be * using simple interest. The Rate Factor for simple interest is calculated by the following formula: @@ -1965,11 +2036,10 @@ public void reAgeEqualAmortization(ProgressiveLoanInterestScheduleModel interest .addCreditedInterestAmount(MathUtil.min(rp.getOutstandingInterest(), rp.getCreditedInterest(), false).negated()); rp.setEmi(rp.getTotalPaidAmount()); rp.moveOutstandingDueToReAging(); - rp.setNoUnrecognisedInterest(true); + rp.setInterestMovedDownward(true); }); - // stop calculate unrecognised interest at this point because all - interestSchedule.getLastRepaymentPeriod().setNoUnrecognisedInterest(true); + collapseIntermediateStubPeriods(interestSchedule); if (!originalMaturityDate.isBefore(transactionDate)) { createRepaymentPeriodForEarlyRepaidAmountsDuringReAgeing(interestSchedule, diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/OverdueBalanceCorrection.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/OverdueBalanceCorrection.java new file mode 100644 index 00000000000..c6ab29a54d8 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/OverdueBalanceCorrection.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.calc.data; + +import java.time.LocalDate; +import org.apache.fineract.organisation.monetary.domain.Money; + +/** + * Records an overdue balance correction applied to the schedule model. Used for detecting and reversing overdue + * corrections on model copies to prevent phantom futureUnrecognizedInterest. + * + * @param correctionDate + * the date where the correction was applied (matches an InterestPeriod's dueDate after IP split) + * @param amount + * the correction amount (+X for inflation, -X for deflation) + * @param affectedRpDueDate + * the dueDate of the RepaymentPeriod that received this correction (for beyond/onTarget detection) + */ +public record OverdueBalanceCorrection(LocalDate correctionDate, Money amount, LocalDate affectedRpDueDate) { +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java index c479d6d3a1b..1dabb865daf 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java @@ -68,6 +68,7 @@ public class ProgressiveLoanInterestScheduleModel { @Setter private LocalDate lastOverdueBalanceChange; + private List overdueCorrections = new ArrayList<>(); public ProgressiveLoanInterestScheduleModel(final List repaymentPeriods, final ILoanConfigurationDetails loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) { @@ -95,9 +96,35 @@ private ProgressiveLoanInterestScheduleModel(final List repayme loanProductRelatedDetail.isInterestRecalculationEnabled())); } - public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { - return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, - installmentAmountInMultiplesOf, mc, false); + public void recordOverdueCorrection(final LocalDate correctionDate, final Money amount, final LocalDate affectedRpDueDate) { + overdueCorrections.add(new OverdueBalanceCorrection(correctionDate, amount, affectedRpDueDate)); + } + + public boolean hasOverdueCorrectionsBeyondDate(final LocalDate targetDueDate) { + return overdueCorrections.stream().anyMatch(oc -> oc.affectedRpDueDate().isAfter(targetDueDate)); + } + + public boolean hasOverdueCorrectionsOnDate(final LocalDate targetDueDate) { + return overdueCorrections.stream().anyMatch(oc -> oc.affectedRpDueDate().isEqual(targetDueDate)); + } + + /** + * Reverses all recorded overdue corrections on this model by subtracting each correction's amount from the + * corresponding InterestPeriod's balanceCorrectionAmount. + */ + public void reverseOverdueCorrections() { + for (final OverdueBalanceCorrection oc : overdueCorrections) { + changeOutstandingBalanceAndUpdateInterestPeriods(oc.correctionDate(), zero(), oc.amount().negated(), zero()); + } + overdueCorrections.clear(); + this.lastOverdueBalanceChange = null; + } + + public ProgressiveLoanInterestScheduleModel deepCopy(final MathContext mc) { + final ProgressiveLoanInterestScheduleModel copy = new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc, false); + copy.overdueCorrections = new ArrayList<>(this.overdueCorrections); + return copy; } public ProgressiveLoanInterestScheduleModel copyWithoutPaidAmounts() { @@ -140,10 +167,18 @@ public Optional findRepaymentPeriodByFromAndDueDate(final Local if (repaymentPeriodDueDate == null) { return Optional.empty(); } - return repaymentPeriods.stream()// - .filter(repaymentPeriodItem -> DateUtils.isEqual(repaymentPeriodItem.getFromDate(), repaymentPeriodFromDate) - && DateUtils.isEqual(repaymentPeriodItem.getDueDate(), repaymentPeriodDueDate))// + // Exact match first + Optional result = repaymentPeriods.stream() + .filter(rp -> DateUtils.isEqual(rp.getFromDate(), repaymentPeriodFromDate) + && DateUtils.isEqual(rp.getDueDate(), repaymentPeriodDueDate)) .findFirst(); + if (result.isEmpty()) { + // Fallback: find a period that encompasses the requested date range + // This handles collapsed stub periods where multiple periods were merged into one + result = repaymentPeriods.stream().filter(rp -> !DateUtils.isAfter(rp.getFromDate(), repaymentPeriodFromDate) + && !DateUtils.isBefore(rp.getDueDate(), repaymentPeriodDueDate)).findFirst(); + } + return result; } public List getRelatedRepaymentPeriods(final LocalDate calculateFromRepaymentPeriodDueDate) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java index 95011fb9558..eca58ee334b 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java @@ -77,7 +77,7 @@ public class RepaymentPeriod { private Memo outstandingBalanceCalculation; @Getter @Setter - private boolean isInterestMoved = false; + private boolean isInterestMovedUpward = false; @Setter private Money totalDisbursedAmount; @@ -99,7 +99,7 @@ public class RepaymentPeriod { private Money creditedInterestMovedDueReAge; @Setter @Getter - private boolean noUnrecognisedInterest; + private boolean isInterestMovedDownward; @Setter @Getter private boolean reAged; @@ -111,7 +111,7 @@ public class RepaymentPeriod { protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List interestPeriods, Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc, - ILoanConfigurationDetails loanProductRelatedDetail, boolean noUnrecognisedInterest, boolean reAged, + ILoanConfigurationDetails loanProductRelatedDetail, boolean isInterestMovedDownward, boolean reAged, boolean reAgedEarlyRepaymentHolder, Money fixedInterest) { this.previous = previous; this.fromDate = fromDate; @@ -124,7 +124,7 @@ protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDat this.futureUnrecognizedInterest = futureUnrecognizedInterest; this.mc = mc; this.loanProductRelatedDetail = loanProductRelatedDetail; - this.noUnrecognisedInterest = noUnrecognisedInterest; + this.isInterestMovedDownward = isInterestMovedDownward; this.reAged = reAged; this.reAgedEarlyRepaymentHolder = reAgedEarlyRepaymentHolder; this.fixedInterest = fixedInterest; @@ -151,13 +151,13 @@ public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod rep final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc, - repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), repaymentPeriod.isReAged(), + repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isInterestMovedDownward(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getFixedInterest()); newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge()); newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge()); newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount()); newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount()); - newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved()); + newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward()); newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency()); // There is always at least 1 interest period, by default with same from-due date as repayment period for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) { @@ -170,13 +170,16 @@ public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, R final Money zero = Money.zero(repaymentPeriod.getCurrency(), mc); final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero, - zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isNoUnrecognisedInterest(), + zero, mc, repaymentPeriod.getLoanProductRelatedDetail(), repaymentPeriod.isInterestMovedDownward(), repaymentPeriod.isReAged(), repaymentPeriod.isReAgedEarlyRepaymentHolder(), repaymentPeriod.getFixedInterest()); newRepaymentPeriod.setCreditedPrincipalMovedDueReAge(repaymentPeriod.getCreditedPrincipalMovedDueReAge()); newRepaymentPeriod.setCreditedInterestMovedDueReAge(repaymentPeriod.getCreditedInterestMovedDueReAge()); + if (repaymentPeriod.isInterestMovedDownward()) { + newRepaymentPeriod.setFixedInterest(repaymentPeriod.getPaidInterest()); + } newRepaymentPeriod.setTotalDisbursedAmount(repaymentPeriod.getTotalDisbursedAmount()); newRepaymentPeriod.setTotalCapitalizedIncomeAmount(repaymentPeriod.getTotalCapitalizedIncomeAmount()); - newRepaymentPeriod.setInterestMoved(repaymentPeriod.isInterestMoved()); + newRepaymentPeriod.setInterestMovedUpward(repaymentPeriod.isInterestMovedUpward()); newRepaymentPeriod.setCurrency(repaymentPeriod.getCurrency()); // There is always at least 1 interest period, by default with same from-due date as repayment period for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) { @@ -217,8 +220,9 @@ private BigDecimal calculateRateFactorPlus1() { @NotNull public Money getCalculatedDueInterest() { if (calculatedDueInterestCalculation == null) { - calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, () -> new Object[] { previous, interestPeriods, - futureUnrecognizedInterest, isInterestMoved, totalDisbursedAmount, fixedInterest, reAged }); + calculatedDueInterestCalculation = Memo.of(this::calculateCalculatedDueInterest, + () -> new Object[] { previous, interestPeriods, futureUnrecognizedInterest, isInterestMovedUpward, + isInterestMovedDownward, totalDisbursedAmount, fixedInterest, reAged }); } return calculatedDueInterestCalculation.get(); } @@ -242,7 +246,7 @@ public Money calculateFixedInterestTillDate() { public Money calculateCalculatedDueInterest() { Money calculatedDueInterest = getZero(); - if (!isInterestMoved()) { + if (!isInterestMovedUpward() && !isInterestMovedDownward()) { calculatedDueInterest = Money.of(getEmi().getCurrencyData(), getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add), mc); @@ -367,7 +371,7 @@ public boolean isFullyPaid() { * @return */ public Money getUnrecognizedInterest() { - return noUnrecognisedInterest ? getZero() : getCalculatedDueInterest().minus(getDueInterest(), getMc()); + return MathUtil.negativeToZero(getCalculatedDueInterest().minus(getDueInterest(), getMc()), getMc()); } public Money getCreditedAmounts() { diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index e5751e020a7..f79a834a841 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -27,6 +27,26 @@ apply plugin: 'io.swagger.core.v3.swagger-gradle-plugin' apply plugin: 'com.google.cloud.tools.jib' apply plugin: 'org.springframework.boot' apply plugin: 'se.thinkcode.cucumber-runner' +apply plugin: 'com.docktape.swagger-brake' + +swaggerBrake { + newApi = "${project.buildDir}/resources/main/static/fineract.json" + oldApi = findProperty('apiBaseline') ?: "${projectDir}/config/swagger/fineract-baseline.json" + outputFormats = ['JSON'] + outputFilePath = "${project.buildDir}/swagger-brake" + deprecatedApiDeletionAllowed = true + strictValidation = false +} + +checkBreakingChanges.dependsOn resolve +checkBreakingChanges.onlyIf { + def baseline = findProperty('apiBaseline') ?: "${projectDir}/config/swagger/fineract-baseline.json" + def exists = file(baseline).exists() + if (!exists) { + logger.lifecycle("Skipping checkBreakingChanges: baseline file not found at ${baseline}") + } + exists +} check.dependsOn('cucumber') @@ -96,10 +116,10 @@ configurations { dependencies { implementation project(':fineract-core') - implementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.mockito:mockito-core' - implementation 'org.mockito:mockito-junit-jupiter' - implementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' + testImplementation 'org.junit.jupiter:junit-jupiter-api' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.liquibase:liquibase-core' } @@ -165,7 +185,7 @@ tasks.register('createDB') { description = "Creates the MariaDB Database. Needs database name to be passed (like: -PdbName=someDBname)" doLast { def sql = Sql.newInstance('jdbc:mariadb://localhost:3306/', mysqlUser, mysqlPassword, 'org.mariadb.jdbc.Driver') - sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4") + sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") } } @@ -197,7 +217,7 @@ tasks.register('createMySQLDB') { description = "Creates the MySQL Database. Needs database name to be passed (like: -PdbName=someDBname)" doLast { def sql = Sql.newInstance('jdbc:mysql://localhost:3306/', mysqlUser, mysqlPassword, 'com.mysql.cj.jdbc.Driver') - sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4") + sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") } } diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 35b5daee841..dff3110e8d6 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -42,6 +42,7 @@ dependencies { implementation(project(path: ':fineract-loan-origination')) implementation(project(path: ':fineract-security')) implementation(project(path: ':fineract-working-capital-loan')) + implementation(project(path: ':fineract-mix')) providedRuntime("org.springframework.boot:spring-boot-starter-tomcat") @@ -58,7 +59,7 @@ dependencies { 'org.springframework.boot:spring-boot-starter-web', 'org.springframework.boot:spring-boot-starter-validation', 'org.springframework.boot:spring-boot-starter-security', - "org.springframework.boot:spring-boot-starter-oauth2-authorization-server", + 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server', 'org.springframework.boot:spring-boot-starter-cache', 'org.springframework.boot:spring-boot-starter-oauth2-resource-server', 'org.springframework.boot:spring-boot-starter-actuator', diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java index ca547efb915..5a94ee78fa8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/api/JournalEntriesApiResource.java @@ -99,7 +99,7 @@ public class JournalEntriesApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Journal Entries", description = "The list capability of journal entries can support pagination and sorting.\n\n" + @Operation(summary = "List Journal Entries", operationId = "retrieveAllJournalEntries", description = "The list capability of journal entries can support pagination and sorting.\n\n" + "Example Requests:\n" + "\n" + "journalentries\n" + "\n" + "journalentries?transactionId=PB37X8Y21EQUY4S\n" + "\n" + "journalentries?officeId=1&manualEntriesOnly=true&fromDate=1 July 2013&toDate=15 July 2013&dateFormat=dd MMMM yyyy&locale=en\n" + "\n" + "journalentries?fields=officeName,glAccountName,transactionDate\n" + "\n" + "journalentries?offset=10&limit=50\n" diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java index 66e91b0336d..1833fad3920 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryReadPlatformServiceImpl.java @@ -212,12 +212,12 @@ public JournalEntryData mapRow(final ResultSet rs, @SuppressWarnings("unused") f TransactionTypeEnumData transactionTypeEnumData = null; - if (PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(PortfolioAccountType.fromInt(entityTypeId))) { final LoanTransactionEnumData loanTransactionType = LoanEnumerations .transactionType(JdbcSupport.getInteger(rs, "loanTransactionType")); transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.getId(), loanTransactionType.getCode(), loanTransactionType.getValue()); - } else if (PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) { + } else if (PortfolioAccountType.SAVINGS.equals(PortfolioAccountType.fromInt(entityTypeId))) { final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations .transactionType(JdbcSupport.getInteger(rs, "savingsTransactionType")); transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java new file mode 100644 index 00000000000..fc8b49170d0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.batch.command.internal; + +import static org.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and Create Charge for a Savings Account. It passes the contents of the body from + * the BatchRequest to {@link SavingsAccountChargesApiResource} and gets back the response. This class will also catch + * any errors raised by {@link SavingsAccountChargesApiResource} and map those errors to appropriate status codes in + * BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class CreateSavingsAccountChargeCommandStrategy implements CommandStrategy { + + private final SavingsAccountChargesApiResource savingsAccountChargesApiResource; + + @Override + public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final Long savingsAccountId = Long.parseLong(pathParameters.get(1)); + + // Create a new charge for a savings account + responseBody = savingsAccountChargesApiResource.addSavingsAccountCharge(savingsAccountId, request.getBody()); + + response.setStatusCode(HttpStatus.SC_OK); + // Set the body of the response after Charge has been successfully created + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/COBCatchUpExecutorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/COBCatchUpExecutorHelper.java new file mode 100644 index 00000000000..0605c112bec --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/COBCatchUpExecutorHelper.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.api; + +import jakarta.ws.rs.core.Response; +import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; +import org.apache.fineract.cob.service.COBCatchUpService; + +public final class COBCatchUpExecutorHelper { + + private COBCatchUpExecutorHelper() {} + + public static Response executeLoanCOBCatchUp(COBCatchUpService loanCOBCatchUpService) { + if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) { + return Response.status(Response.Status.BAD_REQUEST).build(); + } + loanCOBCatchUpService.unlockHardLockedLoans(); + OldestCOBProcessedLoanDTO oldestCOBProcessedLoan = loanCOBCatchUpService.getOldestCOBProcessedLoan(); + + if (oldestCOBProcessedLoan.getCobProcessedDate().equals(oldestCOBProcessedLoan.getCobBusinessDate())) { + return Response.status(Response.Status.OK).build(); + } + loanCOBCatchUpService.executeLoanCOBCatchUp(); + return Response.status(Response.Status.ACCEPTED).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java index 8c690ba68ce..4fc055b67be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java @@ -38,7 +38,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.data.COBPartition; import org.apache.fineract.cob.loan.LoanCOBConstant; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.boot.FineractProfiles; @@ -63,7 +63,7 @@ public class InternalCOBApiResource implements InitializingBean { private static final String DATETIME_PATTERN = "dd MMMM yyyy"; - private final RetrieveLoanIdService retrieveLoanIdService; + private final RetrieveLoanIdService retrieveIdService; private final ApiRequestParameterHelper apiRequestParameterHelper; private final ToApiJsonSerializer toApiJsonSerializerForList; private final LoanRepositoryWrapper loanRepositoryWrapper; @@ -90,7 +90,7 @@ public void afterPropertiesSet() throws Exception { public String getCobPartitions(@Context final UriInfo uriInfo, @PathParam("partitionSize") int partitionSize) { LocalDate businessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.BUSINESS_DATE); log.info("RetrieveLoanCOBPartitions is called with partitionSize {} for {}", partitionSize, businessDate); - List loanCOBPartitions = retrieveLoanIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND, + List loanCOBPartitions = retrieveIdService.retrieveLoanCOBPartitions(LoanCOBConstant.NUMBER_OF_DAYS_BEHIND, businessDate, false, partitionSize); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializerForList.serialize(settings, loanCOBPartitions); diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java index f2986612595..e2b4eb01b02 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/LoanCOBCatchUpApiResource.java @@ -32,7 +32,8 @@ import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.data.IsCatchUpRunningDTO; import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; -import org.apache.fineract.cob.service.LoanCOBCatchUpService; +import org.apache.fineract.cob.service.COBCatchUpService; +import org.apache.fineract.cob.service.LoanCOBCatchUpServiceImpl; import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.springframework.stereotype.Component; @@ -43,7 +44,7 @@ @RequiredArgsConstructor public class LoanCOBCatchUpApiResource { - private final Optional loanCOBCatchUpServiceOp; + private final Optional loanCOBCatchUpServiceOp; @GET @Path("oldest-cob-closed") @@ -51,7 +52,7 @@ public class LoanCOBCatchUpApiResource { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Retrieves the oldest COB processed loan", description = "Retrieves the COB business date and the oldest COB processed loan") public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() { - return loanCOBCatchUpServiceOp.map(LoanCOBCatchUpService::getOldestCOBProcessedLoan) + return loanCOBCatchUpServiceOp.map(COBCatchUpService::getOldestCOBProcessedLoan) .orElseThrow(() -> new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name())); } @@ -64,19 +65,8 @@ public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() { @ApiResponse(responseCode = "202", description = "Catch Up has been started") @ApiResponse(responseCode = "400", description = "Catch Up is already running") public Response executeLoanCOBCatchUp() { - return loanCOBCatchUpServiceOp.map(loanCOBCatchUpService -> { - if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) { - return Response.status(Response.Status.BAD_REQUEST).build(); - } - loanCOBCatchUpService.unlockHardLockedLoans(); - OldestCOBProcessedLoanDTO oldestCOBProcessedLoan = loanCOBCatchUpService.getOldestCOBProcessedLoan(); - - if (oldestCOBProcessedLoan.getCobProcessedDate().equals(oldestCOBProcessedLoan.getCobBusinessDate())) { - return Response.status(Response.Status.OK).build(); - } - loanCOBCatchUpService.executeLoanCOBCatchUp(); - return Response.status(Response.Status.ACCEPTED).build(); - }).orElseThrow(() -> new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name())); + return loanCOBCatchUpServiceOp.map(COBCatchUpExecutorHelper::executeLoanCOBCatchUp) + .orElseThrow(() -> new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name())); } @GET @@ -85,6 +75,6 @@ public Response executeLoanCOBCatchUp() { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Retrieves whether Loan COB catch up is running", description = "Retrieves whether Loan COB catch up is running, and the current execution date if it is running.") public IsCatchUpRunningDTO isCatchUpRunning() { - return loanCOBCatchUpServiceOp.map(LoanCOBCatchUpService::isCatchUpRunning).orElseGet(() -> new IsCatchUpRunningDTO(false, null)); + return loanCOBCatchUpServiceOp.map(COBCatchUpService::isCatchUpRunning).orElseGet(() -> new IsCatchUpRunningDTO(false, null)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/WorkingCapitalLoanCOBCatchUpApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/WorkingCapitalLoanCOBCatchUpApiResource.java new file mode 100644 index 00000000000..ef490baa920 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/WorkingCapitalLoanCOBCatchUpApiResource.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.data.IsCatchUpRunningDTO; +import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; +import org.apache.fineract.cob.service.COBCatchUpService; +import org.apache.fineract.cob.service.WorkingCapitalLoanCOBCatchUpServiceImpl; +import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.springframework.stereotype.Component; + +@Path("/v1/working-capital-loans") +@Component +@Tag(name = "Working Capital Loan COB Catch Up", description = "") +@RequiredArgsConstructor +public class WorkingCapitalLoanCOBCatchUpApiResource { + + private final Optional loanCOBCatchUpServiceOp; + + @GET + @Path("oldest-cob-closed") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieves the oldest COB processed Working Capital Loan", description = "Retrieves the COB business date and the oldest COB processed loan") + public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() { + return loanCOBCatchUpServiceOp.map(COBCatchUpService::getOldestCOBProcessedLoan) + .orElseThrow(() -> new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name())); + } + + @POST + @Path("catch-up") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Executes Working Capital Loan COB Catch Up", description = "Executes the Working Capital Loan COB job on every day from the oldest Loan to the current COB business date") + @ApiResponse(responseCode = "200", description = "All loans are up to date") + @ApiResponse(responseCode = "202", description = "Catch Up has been started") + @ApiResponse(responseCode = "400", description = "Catch Up is already running") + public Response executeLoanCOBCatchUp() { + return loanCOBCatchUpServiceOp.map(COBCatchUpExecutorHelper::executeLoanCOBCatchUp) + .orElseThrow(() -> new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name())); + } + + @GET + @Path("is-catch-up-running") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieves whether Working Capital Loan COB catch up is running", description = "Retrieves whether Working Capital Loan COB catch up is running, and the current execution date if it is running.") + public IsCatchUpRunningDTO isCatchUpRunning() { + return loanCOBCatchUpServiceOp.map(COBCatchUpService::isCatchUpRunning).orElseGet(() -> new IsCatchUpRunningDTO(false, null)); + } +} diff --git a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java similarity index 98% rename from fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java rename to fineract-provider/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java index 98f2a0ae8eb..9453e59e86c 100644 --- a/fineract-cob/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/CustomLoanAccountLockRepositoryImpl.java @@ -26,7 +26,7 @@ @Repository @RequiredArgsConstructor -public class CustomLoanAccountLockRepositoryImpl implements CustomLoanAccountLockRepository { +public class CustomLoanAccountLockRepositoryImpl implements CustomLoanAccountLockRepository { @PersistenceContext private EntityManager entityManager; @@ -52,4 +52,5 @@ and lck.lock_owner in ('LOAN_COB_CHUNK_PROCESSING','LOAN_INLINE_COB_PROCESSING') entityManager.createNativeQuery(sql).executeUpdate(); entityManager.flush(); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java index 45f77d9b964..41faf7ef1ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLock.java @@ -18,63 +18,20 @@ */ package org.apache.fineract.cob.domain; -import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.persistence.Version; import java.time.LocalDate; -import java.time.OffsetDateTime; -import lombok.Getter; import lombok.NoArgsConstructor; -import org.apache.fineract.infrastructure.core.service.DateUtils; @Entity @Table(name = "m_loan_account_locks") @NoArgsConstructor -@Getter -public class LoanAccountLock { +public class LoanAccountLock extends AccountLock { - @Id - @Column(name = "loan_id", nullable = false) - private Long loanId; - - @Version - @Column(name = "version") - private Long version; - - @Enumerated(EnumType.STRING) - @Column(name = "lock_owner", nullable = false) - private LockOwner lockOwner; - - @Column(name = "lock_placed_on", nullable = false) - private OffsetDateTime lockPlacedOn; - - @Column(name = "error") - private String error; - - @Column(name = "stacktrace") - private String stacktrace; - - @Column(name = "lock_placed_on_cob_business_date") - private LocalDate lockPlacedOnCobBusinessDate; + private static final long serialVersionUID = 5267165818666471447L; public LoanAccountLock(Long loanId, LockOwner lockOwner, LocalDate lockPlacedOnCobBusinessDate) { - this.loanId = loanId; - this.lockOwner = lockOwner; - this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); - this.lockPlacedOnCobBusinessDate = lockPlacedOnCobBusinessDate; - } - - public void setError(String errorMessage, String stacktrace) { - this.error = errorMessage; - this.stacktrace = stacktrace; + super(loanId, lockOwner, lockPlacedOnCobBusinessDate); } - public void setNewLockOwner(LockOwner newLockOwner) { - this.lockOwner = newLockOwner; - this.lockPlacedOn = DateUtils.getAuditOffsetDateTime(); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java index 08e908fad6b..3724185c46f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java @@ -22,28 +22,31 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +@Repository public interface LoanAccountLockRepository - extends CustomLoanAccountLockRepository, JpaRepository, JpaSpecificationExecutor { + extends AccountLockRepository, JpaRepository, JpaSpecificationExecutor { + @Override Optional findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + @Override void deleteByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + @Override List findAllByLoanIdIn(List loanIds); + @Override boolean existsByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner); + @Override boolean existsByLoanIdAndLockOwnerAndErrorIsNotNull(Long loanId, LockOwner lockOwner); - @Query(""" - delete from LoanAccountLock lck where lck.lockPlacedOnCobBusinessDate is not null and lck.error is not null and - lck.lockOwner in (org.apache.fineract.cob.domain.LockOwner.LOAN_COB_CHUNK_PROCESSING,org.apache.fineract.cob.domain.LockOwner.LOAN_INLINE_COB_PROCESSING) - """) - @Modifying(flushAutomatically = true) - void removeLockByOwner(); - + @Override List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner); + + @Override + void removeByLockOwnerInAndErrorIsNotNullAndLockPlacedOnCobBusinessDateIsNotNull(List lockOwners); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java index 818dd8fd6bb..a5cbe3f2ca4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java @@ -18,18 +18,23 @@ */ package org.apache.fineract.cob.listener; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.loan.LoanLockingService; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.transaction.support.TransactionTemplate; -public class ChunkProcessingLoanItemListener extends AbstractLoanItemListener { +@Slf4j +public class ChunkProcessingLoanItemListener extends AbstractLoanItemListener { - public ChunkProcessingLoanItemListener(LoanLockingService loanLockingService, TransactionTemplate transactionTemplate) { - super(loanLockingService, transactionTemplate); + public ChunkProcessingLoanItemListener(LockingService lockingService, TransactionTemplate transactionTemplate) { + super(lockingService, transactionTemplate); } @Override protected LockOwner getLockOwner() { return LockOwner.LOAN_COB_CHUNK_PROCESSING; } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java index 548c21328ff..7ad279804f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java @@ -18,14 +18,16 @@ */ package org.apache.fineract.cob.listener; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.loan.LoanLockingService; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.transaction.support.TransactionTemplate; -public class InlineCOBLoanItemListener extends AbstractLoanItemListener { +public class InlineCOBLoanItemListener extends AbstractLoanItemListener { - public InlineCOBLoanItemListener(LoanLockingService loanLockingService, TransactionTemplate transactionTemplate) { - super(loanLockingService, transactionTemplate); + public InlineCOBLoanItemListener(LockingService lockingService, TransactionTemplate transactionTemplate) { + super(lockingService, transactionTemplate); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/WorkingCapitalChunkProcessingLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/WorkingCapitalChunkProcessingLoanItemListener.java new file mode 100644 index 00000000000..471a60253fb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/WorkingCapitalChunkProcessingLoanItemListener.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.listener; + +import org.apache.fineract.cob.conditions.BatchWorkerCondition; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionTemplate; + +@Component +@Conditional(BatchWorkerCondition.class) +public class WorkingCapitalChunkProcessingLoanItemListener + extends AbstractLoanItemListener { + + public WorkingCapitalChunkProcessingLoanItemListener( + LockingService workingCapitalLoanAccountLockLockingService, + TransactionTemplate transactionTemplate) { + super(workingCapitalLoanAccountLockLockingService, transactionTemplate); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java index 42f140b3a6e..8a5ae95ec44 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java @@ -18,55 +18,29 @@ */ package org.apache.fineract.cob.loan; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.COBBusinessStepService; -import org.apache.fineract.cob.data.BusinessStepNameAndOrder; +import org.apache.fineract.cob.processor.AbstractItemProcessor; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanModelProcessingService; import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel; -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.annotation.AfterStep; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemProcessor; import org.springframework.lang.NonNull; -@RequiredArgsConstructor -@Slf4j -public abstract class AbstractLoanItemProcessor implements ItemProcessor { +public abstract class AbstractLoanItemProcessor extends AbstractItemProcessor { - private final COBBusinessStepService cobBusinessStepService; private final ProgressiveLoanModelProcessingService progressiveLoanModelProcessingService; - @Setter(AccessLevel.PROTECTED) - private ExecutionContext executionContext; - private LocalDate businessDate; + public AbstractLoanItemProcessor(COBBusinessStepService cobBusinessStepService, + ProgressiveLoanModelProcessingService progressiveLoanModelProcessingService) { + super(cobBusinessStepService); + this.progressiveLoanModelProcessingService = progressiveLoanModelProcessingService; + } - @SuppressWarnings({ "unchecked" }) @Override public Loan process(@NonNull Loan loan) throws Exception { if (needToRebuildModel(loan)) { progressiveLoanModelProcessingService.recalculateModelAndSave(loan.getId()); } - Set businessSteps = (Set) executionContext.get(LoanCOBConstant.BUSINESS_STEPS); - if (businessSteps == null) { - throw new IllegalStateException("No business steps found in the execution context"); - } - TreeMap businessStepMap = getBusinessStepMap(businessSteps); - - Loan alreadyProcessedLoan = cobBusinessStepService.run(businessStepMap, loan); - alreadyProcessedLoan.setLastClosedBusinessDate(businessDate); - return alreadyProcessedLoan; + return super.process(loan); } private boolean needToRebuildModel(Loan loan) { @@ -74,22 +48,9 @@ private boolean needToRebuildModel(Loan loan) { ProgressiveLoanInterestScheduleModel.getModelVersion()); } - private TreeMap getBusinessStepMap(Set businessSteps) { - Map businessStepMap = businessSteps.stream() - .collect(Collectors.toMap(BusinessStepNameAndOrder::getStepOrder, BusinessStepNameAndOrder::getStepName)); - return new TreeMap<>(businessStepMap); - } - - @AfterStep - public ExitStatus afterStep(@NonNull StepExecution stepExecution) { - return ExitStatus.COMPLETED; - } - - protected void setBusinessDate(StepExecution stepExecution) { - this.businessDate = LocalDate.parse( - Objects.requireNonNull( - (String) stepExecution.getJobExecution().getExecutionContext().get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)), - DateTimeFormatter.ISO_DATE); + @Override + public void setLastRun(Loan processedLoan) { + processedLoan.setLastClosedBusinessDate(getBusinessDate()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java index 759c2add2d7..c9ad1c5b44f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java @@ -23,33 +23,33 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.exceptions.LoanReadException; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.cob.exceptions.LockedReadException; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.item.ItemReader; +import org.springframework.data.repository.CrudRepository; import org.springframework.lang.NonNull; @Slf4j @RequiredArgsConstructor -public abstract class AbstractLoanItemReader implements ItemReader { +public abstract class AbstractLoanItemReader> implements ItemReader { - protected final LoanRepository loanRepository; + protected final CrudRepository loanRepository; @Setter(AccessLevel.PROTECTED) private LinkedBlockingQueue remainingData; @Override - public Loan read() throws Exception { + public T read() throws Exception { final Long loanId = remainingData.poll(); if (loanId != null) { try { return loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); } catch (Exception e) { - throw new LoanReadException(loanId, e); + throw new LockedReadException(loanId, e); } } return null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java index f7e2b60bf37..9518ffef960 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java @@ -21,7 +21,9 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.springframework.batch.item.Chunk; @@ -32,7 +34,7 @@ @RequiredArgsConstructor public abstract class AbstractLoanItemWriter extends RepositoryItemWriter { - private final LoanLockingService loanLockingService; + private final LockingService loanLockingService; @Override public void write(@NonNull Chunk items) throws Exception { diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java index b8eda2d144f..01daf033532 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java @@ -18,95 +18,31 @@ */ package org.apache.fineract.cob.loan; -import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; - -import com.google.common.collect.Lists; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.converter.COBParameterConverter; -import org.apache.fineract.cob.data.COBParameter; +import org.apache.fineract.cob.COBConstant; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException; -import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.apache.fineract.cob.tasklet.ApplyCommonLockTasklet; import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.scope.context.ChunkContext; -import org.springframework.batch.core.step.tasklet.Tasklet; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.lang.NonNull; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @Slf4j -@RequiredArgsConstructor -public class ApplyLoanLockTasklet implements Tasklet { - - private static final long NUMBER_OF_RETRIES = 3; - private final FineractProperties fineractProperties; - private final LoanLockingService loanLockingService; - private final RetrieveLoanIdService retrieveLoanIdService; - private final TransactionTemplate transactionTemplate; - - @Override - @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") - public RepeatStatus execute(@NonNull StepContribution contribution, @NonNull ChunkContext chunkContext) - throws LoanLockCannotBeAppliedException { - ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext(); - long numberOfExecutions = contribution.getStepExecution().getCommitCount(); - COBParameter loanCOBParameter = COBParameterConverter.convert(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)); - boolean isCatchUp = CatchUpFlagResolver.resolve(contribution.getStepExecution()); - List loanIds; - if (Objects.isNull(loanCOBParameter) - || (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId())) - || (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) { - loanIds = Collections.emptyList(); - } else { - loanIds = new ArrayList<>( - retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, isCatchUp)); - } - List> loanIdPartitions = Lists.partition(loanIds, getInClauseParameterSizeLimit()); - List accountLocks = new ArrayList<>(); - loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(loanLockingService.findAllByLoanIdIn(loanIdPartition))); - - List toBeProcessedLoanIds = new ArrayList<>(loanIds); - List alreadyLockedAccountIds = accountLocks.stream().map(LoanAccountLock::getLoanId).toList(); +public class ApplyLoanLockTasklet extends ApplyCommonLockTasklet { - toBeProcessedLoanIds.removeAll(alreadyLockedAccountIds); - try { - applyLocks(toBeProcessedLoanIds); - } catch (Exception e) { - if (numberOfExecutions > NUMBER_OF_RETRIES) { - String message = "There was an error applying lock to loan accounts."; - log.error("{}", message, e); - throw new LoanLockCannotBeAppliedException(message, e); - } else { - return RepeatStatus.CONTINUABLE; - } - } - - return RepeatStatus.FINISHED; + public ApplyLoanLockTasklet(FineractProperties fineractProperties, LockingService loanLockingService, + RetrieveIdService retrieveIdService, TransactionTemplate transactionTemplate) { + super(fineractProperties, loanLockingService, retrieveIdService, transactionTemplate); } - private void applyLocks(List toBeProcessedLoanIds) { - transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - - @Override - protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { - loanLockingService.applyLock(toBeProcessedLoanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING); - } - }); + @Override + public String getCOBParameter() { + return COBConstant.COB_PARAMETER; } - private int getInClauseParameterSizeLimit() { - return fineractProperties.getQuery().getInClauseParameterSizeLimit(); + @Override + public LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java index c25a9c33f97..fc33af0c439 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java @@ -20,13 +20,14 @@ import java.util.List; import java.util.concurrent.LinkedBlockingQueue; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.NonNull; -public class InlineCOBLoanItemReader extends AbstractLoanItemReader { +public class InlineCOBLoanItemReader extends AbstractLoanItemReader { public InlineCOBLoanItemReader(LoanRepository loanRepository) { super(loanRepository); @@ -36,7 +37,7 @@ public InlineCOBLoanItemReader(LoanRepository loanRepository) { @SuppressWarnings({ "unchecked" }) public void beforeStep(@NonNull StepExecution stepExecution) { ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); - List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER); + List loanIds = (List) executionContext.get(LoanCOBConstant.COB_PARAMETER); setRemainingData(new LinkedBlockingQueue<>(loanIds)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java index 9a839d32688..07b8acb84c6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java @@ -18,11 +18,13 @@ */ package org.apache.fineract.cob.loan; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; public class InlineCOBLoanItemWriter extends AbstractLoanItemWriter { - public InlineCOBLoanItemWriter(LoanLockingService loanLockingService) { + public InlineCOBLoanItemWriter(LockingService loanLockingService) { super(loanLockingService); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java index 400a3589308..3e7f7f2193f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java @@ -29,11 +29,14 @@ import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBBusinessStep; import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.COBConstant; import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.data.BusinessStepNameAndOrder; import org.apache.fineract.cob.exceptions.CustomJobParameterNotFoundException; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.ActionContext; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; @@ -49,25 +52,30 @@ @Slf4j @RequiredArgsConstructor -public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet { +public class InlineLoanCOBBuildExecutionContextTasklet, B extends COBBusinessStep> + implements Tasklet { private final COBBusinessStepService cobBusinessStepService; private final CustomJobParameterRepository customJobParameterRepository; private final CustomJobParameterResolver customJobParameterResolver; + private final Class businessStepClass; + private final String cobJobName; private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); + public Set resolveBusinessSteps() { + return cobBusinessStepService.getCOBBusinessSteps(businessStepClass, cobJobName); + } + @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { HashMap businessDates = ThreadLocalContextUtil.getBusinessDates(); ThreadLocalContextUtil.setActionContext(ActionContext.COB); - Set cobBusinessSteps = cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, - LoanCOBConstant.LOAN_COB_JOB_NAME); - contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, - getLoanIdsFromJobParameters(chunkContext)); - contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps); + Set cobBusinessSteps = resolveBusinessSteps(); + contribution.getStepExecution().getExecutionContext().put(COBConstant.COB_PARAMETER, getLoanIdsFromJobParameters(chunkContext)); + contribution.getStepExecution().getExecutionContext().put(COBConstant.BUSINESS_STEPS, cobBusinessSteps); String businessDateString = getBusinessDateFromJobParameters(chunkContext); - contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDateString); + contribution.getStepExecution().getExecutionContext().put(COBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDateString); LocalDate businessDate = LocalDate.parse(businessDateString, DateTimeFormatter.ISO_DATE); businessDates.put(BusinessDateType.COB_DATE, businessDate); businessDates.put(BusinessDateType.BUSINESS_DATE, businessDate.plusDays(1)); @@ -76,15 +84,14 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } private String getBusinessDateFromJobParameters(ChunkContext chunkContext) { - Long customJobParameterId = (Long) chunkContext.getStepContext().getJobParameters() - .get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME); + Long customJobParameterId = (Long) chunkContext.getStepContext().getJobParameters().get(COBConstant.BUSINESS_DATE_PARAMETER_NAME); CustomJobParameter customJobParameter = customJobParameterRepository.findById(customJobParameterId) .orElseThrow(() -> new LoanNotFoundException(customJobParameterId)); String parameterJson = customJobParameter.getParameterJson(); Set jobParameters = gson.fromJson(parameterJson, new TypeToken>() {}.getType()); JobParameterDTO businessDateParameter = jobParameters.stream() - .filter(jobParameterDTO -> jobParameterDTO.getParameterName().equals(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)) - .findFirst().orElseThrow(() -> new CustomJobParameterNotFoundException(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)); + .filter(jobParameterDTO -> jobParameterDTO.getParameterName().equals(COBConstant.BUSINESS_DATE_PARAMETER_NAME)).findFirst() + .orElseThrow(() -> new CustomJobParameterNotFoundException(COBConstant.BUSINESS_DATE_PARAMETER_NAME)); return businessDateParameter.getParameterValue(); } @@ -93,8 +100,8 @@ private List getLoanIdsFromJobParameters(ChunkContext chunkContext) { .getCustomJobParameterSet(chunkContext.getStepContext().getStepExecution()) .orElseThrow(() -> new LoanNotFoundException(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY)); JobParameterDTO loanIdsParameter = jobParameters.stream() - .filter(jobParameterDTO -> jobParameterDTO.getParameterName().equals(LoanCOBConstant.LOAN_IDS_PARAMETER_NAME)).findFirst() - .orElseThrow(() -> new CustomJobParameterNotFoundException(LoanCOBConstant.LOAN_IDS_PARAMETER_NAME)); + .filter(jobParameterDTO -> jobParameterDTO.getParameterName().equals(COBConstant.INLINE_IDS_PARAMETER_NAME)).findFirst() + .orElseThrow(() -> new CustomJobParameterNotFoundException(COBConstant.INLINE_IDS_PARAMETER_NAME)); return gson.fromJson(loanIdsParameter.getParameterValue(), new TypeToken>() {}.getType()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java index ad21d3fcf9d..1d4868bb54c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java @@ -25,14 +25,11 @@ public final class LoanCOBConstant extends COBConstant { public static final String JOB_NAME = "LOAN_COB"; public static final String JOB_HUMAN_READABLE_NAME = "Loan COB"; public static final String LOAN_COB_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS"; - public static final String LOAN_COB_PARAMETER = "loanCobParameter"; public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep"; public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB"; - public static final String LOAN_IDS_PARAMETER_NAME = "LoanIds"; public static final String LOAN_COB_PARTITIONER_STEP = "Loan COB partition - Step"; - public static final String PARTITION_KEY = "partition"; private LoanCOBConstant() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java index 61b08ba21b6..c81e92a44e5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java @@ -24,6 +24,7 @@ import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.conditions.BatchManagerCondition; import org.apache.fineract.cob.listener.COBExecutionListenerRunner; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; @@ -70,7 +71,7 @@ public class LoanCOBManagerConfiguration { @Autowired private ApplicationContext applicationContext; @Autowired - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveLoanIdService retrieveIdService; @Autowired private BusinessEventNotifierService businessEventNotifierService; @Autowired @@ -79,7 +80,7 @@ public class LoanCOBManagerConfiguration { @Bean @StepScope public LoanCOBPartitioner partitioner(@Value("#{stepExecution}") StepExecution stepExecution) { - return new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator, stepExecution, + return new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveIdService, jobOperator, stepExecution, LoanCOBConstant.NUMBER_OF_DAYS_BEHIND); } @@ -109,7 +110,7 @@ public ResolveLoanCOBCustomJobParametersTasklet resolveCustomJobParametersTaskle @Bean public StayedLockedLoansTasklet stayedLockedTasklet() { - return new StayedLockedLoansTasklet(businessEventNotifierService, retrieveLoanIdService); + return new StayedLockedLoansTasklet(businessEventNotifierService, retrieveIdService); } @Bean(name = "loanCOBJob") @@ -117,7 +118,8 @@ public Job loanCOBJob(LoanCOBPartitioner partitioner) { return new JobBuilder(JobName.LOAN_COB.name(), jobRepository) // .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) // .start(resolveCustomJobParametersStep()) // - .next(loanCOBStep(partitioner)).next(stayedLockedStep()) // + .next(loanCOBStep(partitioner)) // + .next(stayedLockedStep()) // .incrementer(new RunIdIncrementer()) // .build(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java index b3cfdbc65d9..9bd2559f348 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java @@ -18,42 +18,33 @@ */ package org.apache.fineract.cob.loan; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.common.CommonPartitioner; import org.apache.fineract.cob.data.BusinessStepNameAndOrder; -import org.apache.fineract.cob.data.COBParameter; -import org.apache.fineract.cob.data.COBPartition; -import org.apache.fineract.cob.resolver.BusinessDateResolver; -import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobExecutionNotRunningException; import org.springframework.batch.core.launch.JobOperator; -import org.springframework.batch.core.launch.NoSuchJobExecutionException; import org.springframework.batch.core.partition.support.Partitioner; import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.NonNull; -import org.springframework.util.StopWatch; @Slf4j -@RequiredArgsConstructor -public class LoanCOBPartitioner implements Partitioner { - - public static final String PARTITION_PREFIX = "partition_"; +public class LoanCOBPartitioner extends CommonPartitioner implements Partitioner { private final PropertyService propertyService; private final COBBusinessStepService cobBusinessStepService; - private final RetrieveLoanIdService retrieveLoanIdService; - private final JobOperator jobOperator; - private final StepExecution stepExecution; - private final Long numberOfDays; + + public LoanCOBPartitioner(PropertyService propertyService, COBBusinessStepService cobBusinessStepService, + RetrieveIdService retrieveIdService, JobOperator jobOperator, StepExecution stepExecution, Long numberOfDaysBehind) { + super(jobOperator, stepExecution, numberOfDaysBehind, retrieveIdService); + this.propertyService = propertyService; + this.cobBusinessStepService = cobBusinessStepService; + + } @NonNull @Override @@ -64,54 +55,4 @@ public Map partition(int gridSize) { return getPartitions(partitionSize, cobBusinessSteps); } - private Map getPartitions(int partitionSize, Set cobBusinessSteps) { - if (cobBusinessSteps.isEmpty()) { - stopJobExecution(); - return Map.of(); - } - LocalDate businessDate = BusinessDateResolver.resolve(stepExecution); - boolean isCatchUp = CatchUpFlagResolver.resolve(stepExecution); - StopWatch sw = new StopWatch(); - sw.start(); - List loanCOBPartitions = new ArrayList<>( - retrieveLoanIdService.retrieveLoanCOBPartitions(numberOfDays, businessDate, isCatchUp, partitionSize)); - sw.stop(); - // if there is no loan to be closed, we still would like to create at least one partition - - if (loanCOBPartitions.isEmpty()) { - loanCOBPartitions.add(new COBPartition(0L, 0L, 1L, 0L)); - } - log.info( - "LoanCOBPartitioner found {} loans to be processed as part of COB. {} partitions were created using partition size {}. RetrieveLoanCOBPartitions was executed in {} ms.", - getLoanCount(loanCOBPartitions), loanCOBPartitions.size(), partitionSize, sw.getTotalTimeMillis()); - return loanCOBPartitions.stream().collect(Collectors.toMap(l -> PARTITION_PREFIX + l.getPageNo(), - l -> createNewPartition(cobBusinessSteps, l, businessDate, isCatchUp))); - } - - private long getLoanCount(List loanCOBPartitions) { - return loanCOBPartitions.stream().map(COBPartition::getCount).reduce(0L, Long::sum); - } - - private ExecutionContext createNewPartition(Set cobBusinessSteps, COBPartition loanCOBPartition, - LocalDate businessDate, boolean isCatchUp) { - ExecutionContext executionContext = new ExecutionContext(); - executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps); - executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, - new COBParameter(loanCOBPartition.getMinId(), loanCOBPartition.getMaxId())); - executionContext.put(LoanCOBConstant.PARTITION_KEY, PARTITION_PREFIX + loanCOBPartition.getPageNo()); - executionContext.put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDate.toString()); - executionContext.put(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, Boolean.toString(isCatchUp)); - return executionContext; - } - - private void stopJobExecution() { - Long jobId = stepExecution.getJobExecution().getId(); - try { - jobOperator.stop(jobId); - } catch (NoSuchJobExecutionException | JobExecutionNotRunningException e) { - log.error("There is no running execution for the given execution ID. Execution ID: {}", jobId); - throw new RuntimeException(e); - } - - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java index 17d97f57090..b84b8001593 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java @@ -22,7 +22,11 @@ import org.apache.fineract.cob.common.InitialisationTasklet; import org.apache.fineract.cob.common.ResetContextTasklet; import org.apache.fineract.cob.conditions.BatchWorkerCondition; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LockingService; import org.apache.fineract.cob.listener.ChunkProcessingLoanItemListener; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; @@ -74,12 +78,12 @@ public class LoanCOBWorkerConfiguration { @Autowired private TransactionTemplate transactionTemplate; @Autowired - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveLoanIdService retrieveIdService; @Autowired private FineractProperties fineractProperties; @Autowired - private LoanLockingService loanLockingService; + private LockingService loanLockingService; @Autowired private ProgressiveLoanModelProcessingService progressiveLoanModelProcessingService; @@ -165,7 +169,7 @@ public ChunkProcessingLoanItemListener loanItemListener() { @Bean public ApplyLoanLockTasklet applyLock() { - return new ApplyLoanLockTasklet(fineractProperties, loanLockingService, retrieveLoanIdService, transactionTemplate); + return new ApplyLoanLockTasklet(fineractProperties, loanLockingService, retrieveIdService, transactionTemplate); } @Bean @@ -176,7 +180,7 @@ public ResetContextTasklet resetContext() { @Bean @StepScope public LoanItemReader cobWorkerItemReader() { - return new LoanItemReader(loanRepository, retrieveLoanIdService, loanLockingService); + return new LoanItemReader(loanRepository, new BeforeStepLockingItemReaderHelper<>(retrieveIdService, loanLockingService)); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java index bd00a20ad3e..98aa5603c27 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java @@ -22,6 +22,8 @@ import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.common.ResetContextTasklet; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LockingService; import org.apache.fineract.cob.listener.InlineCOBLoanItemListener; import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; import org.apache.fineract.infrastructure.jobs.service.JobName; @@ -67,14 +69,14 @@ public class LoanInlineCOBConfig { @Autowired private CustomJobParameterResolver customJobParameterResolver; @Autowired - private LoanLockingService loanLockingService; + private LockingService loanLockingService; @Autowired private ProgressiveLoanModelProcessingService progressiveLoanModelProcessingService; @Bean - public InlineLoanCOBBuildExecutionContextTasklet inlineLoanCOBBuildExecutionContextTasklet() { - return new InlineLoanCOBBuildExecutionContextTasklet(cobBusinessStepService, customJobParameterRepository, - customJobParameterResolver); + public InlineLoanCOBBuildExecutionContextTasklet inlineLoanCOBBuildExecutionContextTasklet() { + return new InlineLoanCOBBuildExecutionContextTasklet<>(cobBusinessStepService, customJobParameterRepository, + customJobParameterResolver, LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME); } @Bean @@ -136,7 +138,7 @@ public ResetContextTasklet inlineCOBResetContext() { @Bean public ExecutionContextPromotionListener inlineCobPromotionListener() { ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); - listener.setKeys(new String[] { LoanCOBConstant.LOAN_COB_PARAMETER, LoanCOBConstant.BUSINESS_STEPS, + listener.setKeys(new String[] { LoanCOBConstant.COB_PARAMETER, LoanCOBConstant.BUSINESS_STEPS, LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME }); return listener; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java index f6c52f2df1c..0305897ea50 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java @@ -18,61 +18,29 @@ */ package org.apache.fineract.cob.loan; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.LinkedBlockingQueue; import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.converter.COBParameterConverter; -import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; -import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.resolver.CatchUpFlagResolver; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.annotation.BeforeStep; -import org.springframework.batch.item.ExecutionContext; import org.springframework.lang.NonNull; @Slf4j -public class LoanItemReader extends AbstractLoanItemReader { +public class LoanItemReader extends AbstractLoanItemReader { - private final RetrieveLoanIdService retrieveLoanIdService; - private final LoanLockingService loanLockingService; + private final BeforeStepLockingItemReaderHelper beforeStepLockingItemReaderHelper; - public LoanItemReader(LoanRepository loanRepository, RetrieveLoanIdService retrieveLoanIdService, - LoanLockingService loanLockingService) { + public LoanItemReader(LoanRepository loanRepository, + BeforeStepLockingItemReaderHelper beforeStepLockingItemReaderHelper) { super(loanRepository); - this.retrieveLoanIdService = retrieveLoanIdService; - this.loanLockingService = loanLockingService; + this.beforeStepLockingItemReaderHelper = beforeStepLockingItemReaderHelper; } @BeforeStep - @SuppressWarnings({ "unchecked" }) public void beforeStep(@NonNull StepExecution stepExecution) { - ExecutionContext executionContext = stepExecution.getExecutionContext(); - COBParameter loanCOBParameter = COBParameterConverter.convert(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)); - List loanIds; - boolean isCatchUp = CatchUpFlagResolver.resolve(stepExecution); - if (Objects.isNull(loanCOBParameter) - || (Objects.isNull(loanCOBParameter.getMinAccountId()) && Objects.isNull(loanCOBParameter.getMaxAccountId())) - || (loanCOBParameter.getMinAccountId().equals(0L) && loanCOBParameter.getMaxAccountId().equals(0L))) { - loanIds = Collections.emptyList(); - } else { - loanIds = retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, - isCatchUp); - if (!loanIds.isEmpty()) { - List lockedByCOBChunkProcessingAccountIds = getLoanIdsLockedWithChunkProcessingLock(loanIds); - loanIds.retainAll(lockedByCOBChunkProcessingAccountIds); - } - } - setRemainingData(new LinkedBlockingQueue<>(loanIds)); + setRemainingData(beforeStepLockingItemReaderHelper.filterRemainingData(stepExecution)); } - private List getLoanIdsLockedWithChunkProcessingLock(List loanIds) { - List accountLocks = new ArrayList<>( - loanLockingService.findAllByLoanIdInAndLockOwner(loanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING)); - return accountLocks.stream().map(LoanAccountLock::getLoanId).toList(); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java index 2696ee03a13..28c83148e13 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java @@ -18,11 +18,13 @@ */ package org.apache.fineract.cob.loan; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; public class LoanItemWriter extends AbstractLoanItemWriter { - public LoanItemWriter(LoanLockingService loanLockingService) { + public LoanItemWriter(LockingService loanLockingService) { super(loanLockingService); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingConfiguration.java index 660051907fe..91dae8c06d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingConfiguration.java @@ -18,7 +18,9 @@ */ package org.apache.fineract.cob.loan; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockingService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -38,7 +40,7 @@ public class LoanLockingConfiguration { @Bean @ConditionalOnMissingBean - public LoanLockingService retrieveLoanLockingService() { + public LockingService retrieveLoanLockingService() { return new LoanLockingServiceImpl(jdbcTemplate, fineractProperties, loanAccountLockRepository); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java index 5a90c781f4a..18a47a547c1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanLockingServiceImpl.java @@ -18,79 +18,36 @@ */ package org.apache.fineract.cob.loan; -import java.sql.PreparedStatement; -import java.time.LocalDate; -import java.util.List; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.AbstractLockingService; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; -import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.springframework.jdbc.core.JdbcTemplate; -@RequiredArgsConstructor @Slf4j -public class LoanLockingServiceImpl implements LoanLockingService { +public class LoanLockingServiceImpl extends AbstractLockingService { private static final String BATCH_LOAN_LOCK_INSERT = """ INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date) VALUES (?,?,?,?,?) """; - private final JdbcTemplate jdbcTemplate; - private final FineractProperties fineractProperties; - private final LoanAccountLockRepository loanAccountLockRepository; - - @Override - public void upgradeLock(List accountsToLock, LockOwner lockOwner) { - jdbcTemplate.batchUpdate(""" - UPDATE m_loan_account_locks SET version= version + 1, lock_owner = ?, lock_placed_on = ? WHERE loan_id = ? - """, accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> { - ps.setString(1, lockOwner.name()); - ps.setObject(2, DateUtils.getAuditOffsetDateTime()); - ps.setLong(3, id); - }); - } - - @Override - public List findAllByLoanIdIn(List loanIds) { - return loanAccountLockRepository.findAllByLoanIdIn(loanIds); - } - - @Override - public LoanAccountLock findByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner) { - return loanAccountLockRepository.findByLoanIdAndLockOwner(loanId, lockOwner).orElseGet(() -> { - log.warn("There is no lock for loan account with id: {}", loanId); - return null; - }); - } + private static final String BATCH_LOAN_LOCK_UPGRADE = """ + UPDATE m_loan_account_locks SET version= version + 1, lock_owner = ?, lock_placed_on = ? WHERE loan_id = ? + """; - @Override - public List findAllByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner) { - return loanAccountLockRepository.findAllByLoanIdInAndLockOwner(loanIds, lockOwner); + public LoanLockingServiceImpl(JdbcTemplate jdbcTemplate, FineractProperties fineractProperties, + LoanAccountLockRepository loanAccountLockRepository) { + super(jdbcTemplate, fineractProperties, loanAccountLockRepository); } @Override - public void applyLock(List loanIds, LockOwner lockOwner) { - LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - jdbcTemplate.batchUpdate(BATCH_LOAN_LOCK_INSERT, loanIds, loanIds.size(), (PreparedStatement ps, Long loanId) -> { - ps.setLong(1, loanId); - ps.setLong(2, 1); - ps.setString(3, lockOwner.name()); - ps.setObject(4, DateUtils.getAuditOffsetDateTime()); - ps.setObject(5, cobBusinessDate); - }); + protected String getBatchLoanLockUpgrade() { + return BATCH_LOAN_LOCK_UPGRADE; } @Override - public void deleteByLoanIdInAndLockOwner(List loanIds, LockOwner lockOwner) { - loanAccountLockRepository.deleteByLoanIdInAndLockOwner(loanIds, lockOwner); - } - - private int getInClauseParameterSizeLimit() { - return fineractProperties.getQuery().getInClauseParameterSizeLimit(); + protected String getBatchLoanLockInsert() { + return BATCH_LOAN_LOCK_INSERT; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedIdServiceImpl.java similarity index 93% rename from fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java rename to fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedIdServiceImpl.java index cdc09d38d5b..8fa29493dfc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedIdServiceImpl.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.cob.loan; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -30,6 +28,8 @@ import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.data.COBPartition; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; @@ -38,7 +38,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @RequiredArgsConstructor -public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdService { +public class RetrieveAllNonClosedIdServiceImpl implements RetrieveLoanIdService { private static final Collection NON_CLOSED_LOAN_STATUSES = new ArrayList<>( Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, LoanStatus.APPROVED, LoanStatus.ACTIVE, @@ -67,11 +67,7 @@ public List retrieveLoanCOBPartitions(Long numberOfDays, LocalDate parameters.addValue("pageSize", partitionSize); parameters.addValue("statusIds", List.of(100, 200, 300, 303, 304)); parameters.addValue("businessDate", businessDate.minusDays(numberOfDays)); - return namedParameterJdbcTemplate.query(sql.toString(), parameters, RetrieveAllNonClosedLoanIdServiceImpl::mapRow); - } - - private static COBPartition mapRow(ResultSet rs, int rowNum) throws SQLException { - return new COBPartition(rs.getLong("min"), rs.getLong("max"), rs.getLong("page"), rs.getLong("count")); + return namedParameterJdbcTemplate.query(sql.toString(), parameters, RetrieveIdService::mapRow); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java index 43aa60e6e15..1c18aa52412 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.cob.loan; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -37,6 +38,6 @@ public class RetrieveLoanIdConfiguration { @Bean @ConditionalOnMissingBean public RetrieveLoanIdService retrieveLoanIdService() { - return new RetrieveAllNonClosedLoanIdServiceImpl(loanRepository, namedParameterJdbcTemplate); + return new RetrieveAllNonClosedIdServiceImpl(loanRepository, namedParameterJdbcTemplate); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java index 2bc4f773b90..b23e58148f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/StayedLockedLoansTasklet.java @@ -26,6 +26,7 @@ import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo; import org.apache.fineract.cob.data.LoanAccountStayedLockedData; import org.apache.fineract.cob.data.LoanAccountsStayedLockedData; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; @@ -39,7 +40,7 @@ public class StayedLockedLoansTasklet implements Tasklet { private final BusinessEventNotifierService businessEventNotifierService; - private final RetrieveLoanIdService retrieveLoanIdService; + private final RetrieveIdService retrieveIdService; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { @@ -52,7 +53,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon private LoanAccountsStayedLockedData buildLoanAccountData() { LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - List stayedLockedLoanAccounts = retrieveLoanIdService + List stayedLockedLoanAccounts = retrieveIdService .findAllStayedLockedByCobBusinessDate(cobBusinessDate); List loanAccounts = new ArrayList<>(); stayedLockedLoanAccounts.forEach(loanAccount -> { diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalInlineCOBLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalInlineCOBLoanItemReader.java new file mode 100644 index 00000000000..c8407525529 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalInlineCOBLoanItemReader.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.loan; + +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.lang.NonNull; + +public class WorkingCapitalInlineCOBLoanItemReader extends AbstractLoanItemReader { + + public WorkingCapitalInlineCOBLoanItemReader(WorkingCapitalLoanRepository loanRepository) { + super(loanRepository); + } + + @BeforeStep + @SuppressWarnings({ "unchecked" }) + public void beforeStep(@NonNull StepExecution stepExecution) { + ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); + List loanIds = (List) executionContext.get(COBConstant.COB_PARAMETER); + setRemainingData(new LinkedBlockingQueue<>(loanIds)); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalLoanInlineCOBConfig.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalLoanInlineCOBConfig.java new file mode 100644 index 00000000000..1413596f8b7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/WorkingCapitalLoanInlineCOBConfig.java @@ -0,0 +1,147 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.loan; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.common.CustomJobParameterResolver; +import org.apache.fineract.cob.common.ResetContextTasklet; +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.workingcapitalloan.InlineWorkingCapitalLoanCOBWorkerItemListener; +import org.apache.fineract.cob.workingcapitalloan.InlineWorkingCapitalLoanCOBWorkerItemWriter; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanInlineCOBWorkerItemProcessor; +import org.apache.fineract.cob.workingcapitalloan.businessstep.WorkingCapitalLoanCOBBusinessStep; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.springbatch.PropertyService; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.listener.ExecutionContextPromotionListener; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +@Configuration +@EnableBatchIntegration +@Conditional(LoanCOBEnabledCondition.class) +@RequiredArgsConstructor +public class WorkingCapitalLoanInlineCOBConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager transactionManager; + private final PropertyService propertyService; + private final COBBusinessStepService cobBusinessStepService; + private final TransactionTemplate transactionTemplate; + private final CustomJobParameterRepository customJobParameterRepository; + private final CustomJobParameterResolver customJobParameterResolver; + private final LockingService loanLockingService; + private final WorkingCapitalLoanRepository loanRepository; + + @Bean + public InlineLoanCOBBuildExecutionContextTasklet inlineWorkingCapitalLoanCOBBuildExecutionContextTasklet() { + return new InlineLoanCOBBuildExecutionContextTasklet<>(cobBusinessStepService, customJobParameterRepository, + customJobParameterResolver, WorkingCapitalLoanCOBBusinessStep.class, + WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_JOB_NAME); + } + + @Bean + protected Step inlineWorkingCapitalLoanCOBBuildExecutionContextStep( + InlineLoanCOBBuildExecutionContextTasklet inlineWorkingCapitalLoanCOBBuildExecutionContextTasklet, + ExecutionContextPromotionListener inlineWorkingCapitalLoanCobPromotionListener) { + return new StepBuilder("Inline COB build execution context step", jobRepository) + .tasklet(inlineWorkingCapitalLoanCOBBuildExecutionContextTasklet, transactionManager) + .listener(inlineWorkingCapitalLoanCobPromotionListener).build(); + } + + @Bean + public Step inlineWorkingCapitalLoanCOBStep(WorkingCapitalInlineCOBLoanItemReader inlineWorkingCapitalLoanCobWorkerItemReader, + WorkingCapitalLoanInlineCOBWorkerItemProcessor inlineWorkingCapitalLoanCobWorkerItemProcessor, + InlineWorkingCapitalLoanCOBWorkerItemWriter inlineWorkingCapitalLoanCobWorkerItemWriter, + InlineWorkingCapitalLoanCOBWorkerItemListener inlineWorkingCapitalLoanCobLoanItemListener) { + return new StepBuilder("Inline Working Capital Loan COB Step", jobRepository) + .chunk(propertyService.getChunkSize(JobName.WORKING_CAPITAL_LOAN_COB_JOB.name()), + transactionManager) + .reader(inlineWorkingCapitalLoanCobWorkerItemReader).processor(inlineWorkingCapitalLoanCobWorkerItemProcessor) + .writer(inlineWorkingCapitalLoanCobWorkerItemWriter).listener(inlineWorkingCapitalLoanCobLoanItemListener).build(); + } + + @Bean(name = "inlineWorkingCapitalLoanCOBJob") + public Job inlineWorkingCapitalLoanCOBJob(Step inlineWorkingCapitalLoanCOBBuildExecutionContextStep, + Step inlineWorkingCapitalLoanCOBStep, Step inlineWorkingCapitalLoanCOBResetContextStep) { + return new JobBuilder(WorkingCapitalLoanCOBConstant.INLINE_WORKING_CAPITAL_LOAN_COB_JOB_NAME, jobRepository) // + .start(inlineWorkingCapitalLoanCOBBuildExecutionContextStep).next(inlineWorkingCapitalLoanCOBStep) + .next(inlineWorkingCapitalLoanCOBResetContextStep) // + .incrementer(new RunIdIncrementer()) // + .build(); + } + + @JobScope + @Bean + public WorkingCapitalInlineCOBLoanItemReader inlineWorkingCapitalLoanCobWorkerItemReader() { + return new WorkingCapitalInlineCOBLoanItemReader(loanRepository); + } + + @JobScope + @Bean + public WorkingCapitalLoanInlineCOBWorkerItemProcessor inlineWorkingCapitalLoanCobWorkerItemProcessor() { + return new WorkingCapitalLoanInlineCOBWorkerItemProcessor(cobBusinessStepService); + } + + @Bean + public Step inlineWorkingCapitalLoanCOBResetContextStep(ResetContextTasklet inlineWorkingCapitalLoanCOBResetContext) { + return new StepBuilder("Reset context - Step", jobRepository).tasklet(inlineWorkingCapitalLoanCOBResetContext, transactionManager) + .build(); + } + + @Bean + public InlineWorkingCapitalLoanCOBWorkerItemWriter inlineWorkingCapitalLoanCobWorkerItemWriter() { + return new InlineWorkingCapitalLoanCOBWorkerItemWriter(loanLockingService, loanRepository); + } + + @Bean + public InlineWorkingCapitalLoanCOBWorkerItemListener inlineWorkingCapitalLoanCobLoanItemListener() { + return new InlineWorkingCapitalLoanCOBWorkerItemListener(loanLockingService, transactionTemplate); + } + + @Bean + public ResetContextTasklet inlineWorkingCapitalLoanCOBResetContext() { + return new ResetContextTasklet(); + } + + @Bean + public ExecutionContextPromotionListener inlineWorkingCapitalLoanCobPromotionListener() { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + listener.setKeys(new String[] { COBConstant.COB_PARAMETER, COBConstant.BUSINESS_STEPS, COBConstant.BUSINESS_DATE_PARAMETER_NAME }); + return listener; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCOBExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCOBExecutorService.java new file mode 100644 index 00000000000..fd149cd9cd3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCOBExecutorService.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import org.apache.fineract.infrastructure.core.domain.FineractContext; + +public interface AsyncCOBExecutorService { + + void executeLoanCOBCatchUpAsync(FineractContext context); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCommonCOBExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCommonCOBExecutorService.java new file mode 100644 index 00000000000..b9897d72099 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncCommonCOBExecutorService.java @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.config.TaskExecutorConstant; +import org.apache.fineract.infrastructure.core.domain.FineractContext; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; +import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail; +import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository; +import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; +import org.apache.fineract.infrastructure.jobs.service.JobStarter; +import org.apache.fineract.infrastructure.jobs.service.SchedulerServiceConstants; +import org.quartz.JobExecutionException; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.scheduling.annotation.Async; + +@Slf4j +@RequiredArgsConstructor +public abstract class AsyncCommonCOBExecutorService implements AsyncCOBExecutorService { + + private final JobLocator jobLocator; + private final ScheduledJobDetailRepository scheduledJobDetailRepository; + private final JobStarter jobStarter; + private final RetrieveIdService retrieveIdService; + + @Override + @Async(TaskExecutorConstant.LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME) + public void executeLoanCOBCatchUpAsync(FineractContext context) { + try { + ThreadLocalContextUtil.init(context); + LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); + List loanIdAndLastClosedBusinessDate = retrieveIdService + .retrieveLoanIdsOldestCobProcessed(cobBusinessDate); + + LocalDate oldestCOBProcessedDate = !loanIdAndLastClosedBusinessDate.isEmpty() + ? loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate() + : cobBusinessDate; + if (DateUtils.isBefore(oldestCOBProcessedDate, cobBusinessDate)) { + executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate, cobBusinessDate); + } + } catch (NoSuchJobException e) { + // Throwing an error here is useless as it will be swallowed hence it is async method + log.error("Job not found: {}", getJobName(), new JobNotFoundException(getJobName(), e)); + } catch (JobInstanceAlreadyCompleteException | JobRestartException | JobParametersInvalidException + | JobExecutionAlreadyRunningException | JobExecutionException e) { + // Throwing an error here is useless as it will be swallowed hence it is async method + log.error("Error executing job", e); + } finally { + ThreadLocalContextUtil.reset(); + } + } + + public abstract String getJobName(); + + public abstract String getJobHumanReadableName(); + + private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate oldestCOBProcessedDate, LocalDate cobBusinessDate) + throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, + JobParametersInvalidException, JobRestartException, JobExecutionException { + Job job = jobLocator.getJob(getJobName()); + ScheduledJobDetail scheduledJobDetail = scheduledJobDetailRepository.findByJobName(getJobHumanReadableName()); + LocalDate executingBusinessDate = oldestCOBProcessedDate.plusDays(1); + String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); + + while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { + JobParameterDTO jobParameterDTO = new JobParameterDTO(COBConstant.BUSINESS_DATE_PARAMETER_NAME, + executingBusinessDate.format(DateTimeFormatter.ISO_DATE)); + JobParameterDTO jobParameterCatchUpDTO = new JobParameterDTO(COBConstant.IS_CATCH_UP_PARAMETER_NAME, "true"); + JobParameterDTO tenantParameterDTO = new JobParameterDTO(SchedulerServiceConstants.TENANT_IDENTIFIER, tenantIdentifier); + Set jobParameters = new HashSet<>(); + Collections.addAll(jobParameters, jobParameterDTO, jobParameterCatchUpDTO, tenantParameterDTO); + jobStarter.run(job, scheduledJobDetail, jobParameters, tenantIdentifier); + executingBusinessDate = executingBusinessDate.plusDays(1); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java index e2001f31f56..ca82d3ce6cf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorService.java @@ -18,9 +18,4 @@ */ package org.apache.fineract.cob.service; -import org.apache.fineract.infrastructure.core.domain.FineractContext; - -public interface AsyncLoanCOBExecutorService { - - void executeLoanCOBCatchUpAsync(FineractContext context); -} +public interface AsyncLoanCOBExecutorService extends AsyncCOBExecutorService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java index 8de65836790..9d53e73d80f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java @@ -18,96 +18,41 @@ */ package org.apache.fineract.cob.service; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; import org.apache.fineract.cob.loan.LoanCOBConstant; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.TaskExecutorConstant; import org.apache.fineract.infrastructure.core.domain.FineractContext; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; -import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail; import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository; -import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; import org.apache.fineract.infrastructure.jobs.service.JobStarter; -import org.apache.fineract.infrastructure.jobs.service.SchedulerServiceConstants; -import org.quartz.JobExecutionException; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.configuration.JobLocator; -import org.springframework.batch.core.launch.NoSuchJobException; -import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; -import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; -import org.springframework.batch.core.repository.JobRestartException; import org.springframework.context.annotation.Conditional; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Slf4j @Service -@RequiredArgsConstructor @Conditional(LoanCOBEnabledCondition.class) -public class AsyncLoanCOBExecutorServiceImpl implements AsyncLoanCOBExecutorService { +public class AsyncLoanCOBExecutorServiceImpl extends AsyncCommonCOBExecutorService implements AsyncLoanCOBExecutorService { - private final JobLocator jobLocator; - private final ScheduledJobDetailRepository scheduledJobDetailRepository; - private final JobStarter jobStarter; - private final RetrieveLoanIdService retrieveLoanIdService; + public AsyncLoanCOBExecutorServiceImpl(JobLocator jobLocator, ScheduledJobDetailRepository scheduledJobDetailRepository, + JobStarter jobStarter, RetrieveLoanIdService retrieveIdService) { + super(jobLocator, scheduledJobDetailRepository, jobStarter, retrieveIdService); + } @Override @Async(TaskExecutorConstant.LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME) public void executeLoanCOBCatchUpAsync(FineractContext context) { - try { - ThreadLocalContextUtil.init(context); - LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - List loanIdAndLastClosedBusinessDate = retrieveLoanIdService - .retrieveLoanIdsOldestCobProcessed(cobBusinessDate); - - LocalDate oldestCOBProcessedDate = !loanIdAndLastClosedBusinessDate.isEmpty() - ? loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate() - : cobBusinessDate; - if (DateUtils.isBefore(oldestCOBProcessedDate, cobBusinessDate)) { - executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate, cobBusinessDate); - } - } catch (NoSuchJobException e) { - // Throwing an error here is useless as it will be swallowed hence it is async method - log.error("Job not found: {}", LoanCOBConstant.JOB_NAME, new JobNotFoundException(LoanCOBConstant.JOB_NAME, e)); - } catch (JobInstanceAlreadyCompleteException | JobRestartException | JobParametersInvalidException - | JobExecutionAlreadyRunningException | JobExecutionException e) { - // Throwing an error here is useless as it will be swallowed hence it is async method - log.error("Error executing job", e); - } finally { - ThreadLocalContextUtil.reset(); - } + super.executeLoanCOBCatchUpAsync(context); } - private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate oldestCOBProcessedDate, LocalDate cobBusinessDate) - throws NoSuchJobException, JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, - JobParametersInvalidException, JobRestartException, JobExecutionException { - Job job = jobLocator.getJob(LoanCOBConstant.JOB_NAME); - ScheduledJobDetail scheduledJobDetail = scheduledJobDetailRepository.findByJobName(LoanCOBConstant.JOB_HUMAN_READABLE_NAME); - LocalDate executingBusinessDate = oldestCOBProcessedDate.plusDays(1); - String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); + @Override + public String getJobName() { + return LoanCOBConstant.JOB_NAME; + } - while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { - JobParameterDTO jobParameterDTO = new JobParameterDTO(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, - executingBusinessDate.format(DateTimeFormatter.ISO_DATE)); - JobParameterDTO jobParameterCatchUpDTO = new JobParameterDTO(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "true"); - JobParameterDTO tenantParameterDTO = new JobParameterDTO(SchedulerServiceConstants.TENANT_IDENTIFIER, tenantIdentifier); - Set jobParameters = new HashSet<>(); - Collections.addAll(jobParameters, jobParameterDTO, jobParameterCatchUpDTO, tenantParameterDTO); - jobStarter.run(job, scheduledJobDetail, jobParameters, tenantIdentifier); - executingBusinessDate = executingBusinessDate.plusDays(1); - } + @Override + public String getJobHumanReadableName() { + return LoanCOBConstant.JOB_HUMAN_READABLE_NAME; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorService.java new file mode 100644 index 00000000000..0be3f76579c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorService.java @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +public interface AsyncWorkingCapitalLoanCOBExecutorService extends AsyncCOBExecutorService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorServiceImpl.java new file mode 100644 index 00000000000..4564712a900 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncWorkingCapitalLoanCOBExecutorServiceImpl.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanRetrieveIdService; +import org.apache.fineract.infrastructure.core.config.TaskExecutorConstant; +import org.apache.fineract.infrastructure.core.domain.FineractContext; +import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.jobs.service.JobStarter; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.context.annotation.Conditional; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@Conditional(LoanCOBEnabledCondition.class) +public class AsyncWorkingCapitalLoanCOBExecutorServiceImpl extends AsyncCommonCOBExecutorService + implements AsyncWorkingCapitalLoanCOBExecutorService { + + public AsyncWorkingCapitalLoanCOBExecutorServiceImpl(JobLocator jobLocator, ScheduledJobDetailRepository scheduledJobDetailRepository, + JobStarter jobStarter, WorkingCapitalLoanRetrieveIdService retrieveIdService) { + super(jobLocator, scheduledJobDetailRepository, jobStarter, retrieveIdService); + } + + @Override + @Async(TaskExecutorConstant.WORKING_CAPITAL_LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME) + public void executeLoanCOBCatchUpAsync(FineractContext context) { + super.executeLoanCOBCatchUpAsync(context); + } + + @Override + public String getJobName() { + return JobName.WORKING_CAPITAL_LOAN_COB_JOB.name(); + } + + @Override + public String getJobHumanReadableName() { + return WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_JOB_HUMAN_READABLE_NAME; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/COBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/COBCatchUpService.java new file mode 100644 index 00000000000..5d793ab9013 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/COBCatchUpService.java @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import org.apache.fineract.cob.data.IsCatchUpRunningDTO; +import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; + +public interface COBCatchUpService { + + void unlockHardLockedLoans(); + + OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan(); + + void executeLoanCOBCatchUp(); + + IsCatchUpRunningDTO isCatchUpRunning(); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/CommonCOBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/CommonCOBCatchUpService.java new file mode 100644 index 00000000000..de102e0bbee --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/CommonCOBCatchUpService.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.cob.service; + +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.cob.data.IsCatchUpRunningDTO; +import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; +import org.apache.fineract.cob.domain.AccountLock; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.domain.FineractContext; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository; + +@RequiredArgsConstructor +public abstract class CommonCOBCatchUpService implements COBCatchUpService { + + private final AsyncCOBExecutorService asyncLoanCOBExecutorService; + private final JobExecutionRepository jobExecutionRepository; + private final RetrieveIdService retrieveIdService; + private final AccountLockService accountLockService; + + @Override + public void unlockHardLockedLoans() { + accountLockService.updateCobAndRemoveLocks(); + } + + @Override + public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() { + List loanIdAndLastClosedBusinessDate = retrieveIdService + .retrieveLoanIdsOldestCobProcessed(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)); + OldestCOBProcessedLoanDTO oldestCOBProcessedLoanDTO = new OldestCOBProcessedLoanDTO(); + oldestCOBProcessedLoanDTO.setLoanIds(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getId).toList()); + oldestCOBProcessedLoanDTO + .setCobProcessedDate(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate) + .findFirst().orElse(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE))); + oldestCOBProcessedLoanDTO.setCobBusinessDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)); + return oldestCOBProcessedLoanDTO; + } + + @Override + public void executeLoanCOBCatchUp() { + FineractContext context = ThreadLocalContextUtil.getContext(); + asyncLoanCOBExecutorService.executeLoanCOBCatchUpAsync(context); + } + + @Override + public IsCatchUpRunningDTO isCatchUpRunning() { + LocalDate runningCatchUpBusinessDate = jobExecutionRepository.getBusinessDateOfRunningJobByExecutionParameter(getJobName(), + COBConstant.COB_CUSTOM_JOB_PARAMETER_KEY, COBConstant.IS_CATCH_UP_PARAMETER_NAME, "true", + COBConstant.BUSINESS_DATE_PARAMETER_NAME); + return new IsCatchUpRunningDTO(runningCatchUpBusinessDate != null, runningCatchUpBusinessDate); + } + + public abstract String getJobName(); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineCommonLockableCOBExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineCommonLockableCOBExecutorService.java new file mode 100644 index 00000000000..f1ba533bfe3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineCommonLockableCOBExecutorService.java @@ -0,0 +1,256 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.cob.domain.AccountLock; +import org.apache.fineract.cob.domain.AccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException; +import org.apache.fineract.infrastructure.core.exception.PlatformRequestBodyItemLimitValidationException; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; +import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.lang.NonNull; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Slf4j +@RequiredArgsConstructor +public abstract class InlineCommonLockableCOBExecutorService implements InlineExecutorService { + + private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: "; + private final AccountLockRepository loanAccountLockRepository; + private final InlineLoanCOBExecutionDataParser dataParser; + private final JobLauncher jobLauncher; + private final JobLocator jobLocator; + private final JobExplorer jobExplorer; + private final TransactionTemplate transactionTemplate; + private final CustomJobParameterRepository customJobParameterRepository; + private final PlatformSecurityContext context; + private final RetrieveIdService retrieveIdService; + private final FineractProperties fineractProperties; + + private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); + + public abstract T createAccountLock(Long loanId, LockOwner loanInlineCobProcessing, LocalDate businessDate); + + @Override + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws AccountLockCannotBeOverruledException { + List loanIds = dataParser.parseExecution(command); + validateLoanIdsListSize(loanIds); + execute(loanIds, jobName); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build(); + } + + @Override + public void execute(List loanIds, String jobName) { + LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); + List loansToBeProcessed = getLoansToBeProcessed(loanIds, cobBusinessDate); + LocalDate executingBusinessDate = getOldestCOBBusinessDate(loansToBeProcessed).plusDays(1); + if (!loansToBeProcessed.isEmpty()) { + while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { + execute(getLoanIdsToBeProcessed(loansToBeProcessed, executingBusinessDate), jobName, executingBusinessDate); + executingBusinessDate = executingBusinessDate.plusDays(1); + } + } + } + + private List getLoanIdsToBeProcessed(List loansToBeProcessed, LocalDate executingBusinessDate) { + List loanIdsToBeProcessed = new ArrayList<>(); + loansToBeProcessed.forEach(loan -> { + if (loan.getLastClosedBusinessDate() != null) { + if (DateUtils.isBefore(loan.getLastClosedBusinessDate(), executingBusinessDate)) { + loanIdsToBeProcessed.add(loan.getId()); + } + } else { + loanIdsToBeProcessed.add(loan.getId()); + } + }); + return loanIdsToBeProcessed; + } + + @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") + private void execute(List loanIds, String jobName, LocalDate businessDate) { + lockLoanAccounts(loanIds, businessDate); + Job inlineLoanCOBJob; + try { + inlineLoanCOBJob = jobLocator.getJob(jobName); + } catch (NoSuchJobException e) { + throw new JobNotFoundException(jobName, e); + } + JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob) + .addJobParameters(new JobParameters(getJobParametersMap(loanIds, businessDate))).toJobParameters(); + JobExecution jobExecution; + try { + jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters); + } catch (Exception e) { + log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); + throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); + } + if (!BatchStatus.COMPLETED.equals(jobExecution.getStatus())) { + log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName); + throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName); + } + } + + private LocalDate getOldestCOBBusinessDate(List loans) { + COBIdAndLastClosedBusinessDate oldestLoan = loans.stream().min(Comparator + .comparing(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate, Comparator.nullsLast(Comparator.naturalOrder()))) + .orElse(null); + return oldestLoan != null && oldestLoan.getLastClosedBusinessDate() != null ? oldestLoan.getLastClosedBusinessDate() + : ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(1); + } + + private List getLoansToBeProcessed(List loanIds, LocalDate cobBusinessDate) { + List loanIdAndLastClosedBusinessDates = new ArrayList<>(); + List> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); + partitions.forEach(partition -> loanIdAndLastClosedBusinessDates + .addAll(retrieveIdService.retrieveLoanIdsBehindDateOrNull(cobBusinessDate, partition))); + return loanIdAndLastClosedBusinessDates; + } + + private List getLoanAccountLocks(List loanIds, LocalDate businessDate) { + List loanAccountLocks = new ArrayList<>(); + List alreadyLockedLoanIds = new ArrayList<>(); + loanIds.forEach(loanId -> { + Optional loanLockOptional = loanAccountLockRepository.findById(loanId); + if (loanLockOptional.isPresent()) { + T loanAccountLock = loanLockOptional.get(); + if (isLockOverrulable(loanAccountLock)) { + loanAccountLocks.add(loanAccountLock); + } else { + alreadyLockedLoanIds.add(loanId); + } + } else { + loanAccountLocks.add(createAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING, businessDate)); + } + }); + if (!alreadyLockedLoanIds.isEmpty()) { + String message = "There is a hard lock on the loan account without any error, so it can't be overruled."; + String loanIdsMessage = " Locked loan IDs: " + alreadyLockedLoanIds; + throw new AccountLockCannotBeOverruledException(message + loanIdsMessage); + } + + return loanAccountLocks; + } + + private Long saveCustomJobParameter(String paramName, String paramValue) { + JobParameterDTO paramDTO = new JobParameterDTO(paramName, paramValue); + Set paramSet = Collections.singleton(paramDTO); + return customJobParameterRepository.save(paramSet); + } + + private Map> getJobParametersMap(List loanIds, LocalDate businessDate) { + String parameterJson = gson.toJson(loanIds); + Long loanIdsJobParameterId = saveCustomJobParameter(COBConstant.INLINE_IDS_PARAMETER_NAME, parameterJson); + Long businessDateJobParameterId = saveCustomJobParameter(COBConstant.BUSINESS_DATE_PARAMETER_NAME, + businessDate.format(DateTimeFormatter.ISO_DATE)); + Map> jobParameterMap = new HashMap<>(); + jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter<>(loanIdsJobParameterId, Long.class)); + jobParameterMap.put(COBConstant.BUSINESS_DATE_PARAMETER_NAME, new JobParameter<>(businessDateJobParameterId, Long.class)); + return jobParameterMap; + } + + private void lockLoanAccounts(List loanIds, LocalDate businessDate) { + transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + + @Override + protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { + List loanAccountLocks = getLoanAccountLocks(loanIds, businessDate); + loanAccountLocks.forEach(loanAccountLock -> { + try { + loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING); + loanAccountLockRepository.saveAndFlush(loanAccountLock); + } catch (Exception e) { + log.error("Error updating lock on loan account. Locked loan ID: {}", loanAccountLock.getLoanId(), e); + throw new AccountLockCannotBeOverruledException( + "Error updating lock on loan account. Locked loan ID: %s".formatted(loanAccountLock.getLoanId()), e); + } + }); + } + }); + } + + private boolean isLockOverrulable(T loanAccountLock) { + if (isBypassUser()) { + return true; + } else { + return StringUtils.isNotBlank(loanAccountLock.getError()); + } + } + + private boolean isBypassUser() { + return context.getAuthenticatedUserIfPresent().isBypassUser(); + } + + private void validateLoanIdsListSize(List loanIds) { + int inlineLoanCobRequestItemLimit = fineractProperties.getApi().getBodyItemSizeLimit().getInlineLoanCob(); + if (loanIds.size() > inlineLoanCobRequestItemLimit) { + String userMessage = "Size of the loan IDs list cannot be over " + inlineLoanCobRequestItemLimit; + throw new PlatformRequestBodyItemLimitValidationException(userMessage); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java index 98d9def4bab..fd601a122b6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -18,243 +18,37 @@ */ package org.apache.fineract.cob.service; -import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; - -import com.google.common.collect.Lists; -import com.google.gson.Gson; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException; -import org.apache.fineract.cob.loan.LoanCOBConstant; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException; -import org.apache.fineract.infrastructure.core.exception.PlatformRequestBodyItemLimitValidationException; -import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO; import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; -import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; -import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.configuration.JobLocator; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.context.annotation.Conditional; -import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @Service @Slf4j -@RequiredArgsConstructor @Conditional(LoanCOBEnabledCondition.class) -public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService { - - private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: "; - private final LoanAccountLockRepository loanAccountLockRepository; - private final InlineLoanCOBExecutionDataParser dataParser; - private final JobLauncher jobLauncher; - private final JobLocator jobLocator; - private final JobExplorer jobExplorer; - private final TransactionTemplate transactionTemplate; - private final CustomJobParameterRepository customJobParameterRepository; - private final PlatformSecurityContext context; - private final RetrieveLoanIdService retrieveLoanIdService; - private final FineractProperties fineractProperties; +public class InlineLoanCOBExecutorServiceImpl extends InlineCommonLockableCOBExecutorService { - private final Gson gson = GoogleGsonSerializerHelper.createSimpleGson(); - - @Override - @Transactional(propagation = Propagation.NOT_SUPPORTED) - public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws AccountLockCannotBeOverruledException { - List loanIds = dataParser.parseExecution(command); - validateLoanIdsListSize(loanIds); - execute(loanIds, jobName); - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build(); + public InlineLoanCOBExecutorServiceImpl(LoanAccountLockRepository loanAccountLockRepository, + InlineLoanCOBExecutionDataParser dataParser, JobLauncher jobLauncher, JobLocator jobLocator, JobExplorer jobExplorer, + TransactionTemplate transactionTemplate, CustomJobParameterRepository customJobParameterRepository, + PlatformSecurityContext context, RetrieveLoanIdService retrieveIdService, FineractProperties fineractProperties) { + super(loanAccountLockRepository, dataParser, jobLauncher, jobLocator, jobExplorer, transactionTemplate, + customJobParameterRepository, context, retrieveIdService, fineractProperties); } @Override - public void execute(List loanIds, String jobName) { - LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE); - List loansToBeProcessed = getLoansToBeProcessed(loanIds, cobBusinessDate); - LocalDate executingBusinessDate = getOldestCOBBusinessDate(loansToBeProcessed).plusDays(1); - if (!loansToBeProcessed.isEmpty()) { - while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) { - execute(getLoanIdsToBeProcessed(loansToBeProcessed, executingBusinessDate), jobName, executingBusinessDate); - executingBusinessDate = executingBusinessDate.plusDays(1); - } - } - } - - private List getLoanIdsToBeProcessed(List loansToBeProcessed, LocalDate executingBusinessDate) { - List loanIdsToBeProcessed = new ArrayList<>(); - loansToBeProcessed.forEach(loan -> { - if (loan.getLastClosedBusinessDate() != null) { - if (DateUtils.isBefore(loan.getLastClosedBusinessDate(), executingBusinessDate)) { - loanIdsToBeProcessed.add(loan.getId()); - } - } else { - loanIdsToBeProcessed.add(loan.getId()); - } - }); - return loanIdsToBeProcessed; - } - - @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") - private void execute(List loanIds, String jobName, LocalDate businessDate) { - lockLoanAccounts(loanIds, businessDate); - Job inlineLoanCOBJob; - try { - inlineLoanCOBJob = jobLocator.getJob(jobName); - } catch (NoSuchJobException e) { - throw new JobNotFoundException(jobName, e); - } - JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob) - .addJobParameters(new JobParameters(getJobParametersMap(loanIds, businessDate))).toJobParameters(); - JobExecution jobExecution; - try { - jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters); - } catch (Exception e) { - log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); - throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); - } - if (!BatchStatus.COMPLETED.equals(jobExecution.getStatus())) { - log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName); - throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName); - } - } - - private LocalDate getOldestCOBBusinessDate(List loans) { - COBIdAndLastClosedBusinessDate oldestLoan = loans.stream().min(Comparator - .comparing(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate, Comparator.nullsLast(Comparator.naturalOrder()))) - .orElse(null); - return oldestLoan != null && oldestLoan.getLastClosedBusinessDate() != null ? oldestLoan.getLastClosedBusinessDate() - : ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(1); - } - - private List getLoansToBeProcessed(List loanIds, LocalDate cobBusinessDate) { - List loanIdAndLastClosedBusinessDates = new ArrayList<>(); - List> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); - partitions.forEach(partition -> loanIdAndLastClosedBusinessDates - .addAll(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(cobBusinessDate, partition))); - return loanIdAndLastClosedBusinessDates; - } - - private List getLoanAccountLocks(List loanIds, LocalDate businessDate) { - List loanAccountLocks = new ArrayList<>(); - List alreadyLockedLoanIds = new ArrayList<>(); - loanIds.forEach(loanId -> { - Optional loanLockOptional = loanAccountLockRepository.findById(loanId); - if (loanLockOptional.isPresent()) { - LoanAccountLock loanAccountLock = loanLockOptional.get(); - if (isLockOverrulable(loanAccountLock)) { - loanAccountLocks.add(loanAccountLock); - } else { - alreadyLockedLoanIds.add(loanId); - } - } else { - loanAccountLocks.add(new LoanAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING, businessDate)); - } - }); - if (!alreadyLockedLoanIds.isEmpty()) { - String message = "There is a hard lock on the loan account without any error, so it can't be overruled."; - String loanIdsMessage = " Locked loan IDs: " + alreadyLockedLoanIds; - throw new AccountLockCannotBeOverruledException(message + loanIdsMessage); - } - - return loanAccountLocks; - } - - private Long saveCustomJobParameter(String paramName, String paramValue) { - JobParameterDTO paramDTO = new JobParameterDTO(paramName, paramValue); - Set paramSet = Collections.singleton(paramDTO); - return customJobParameterRepository.save(paramSet); - } - - private Map> getJobParametersMap(List loanIds, LocalDate businessDate) { - String parameterJson = gson.toJson(loanIds); - Long loanIdsJobParameterId = saveCustomJobParameter(LoanCOBConstant.LOAN_IDS_PARAMETER_NAME, parameterJson); - Long businessDateJobParameterId = saveCustomJobParameter(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, - businessDate.format(DateTimeFormatter.ISO_DATE)); - Map> jobParameterMap = new HashMap<>(); - jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter<>(loanIdsJobParameterId, Long.class)); - jobParameterMap.put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, new JobParameter<>(businessDateJobParameterId, Long.class)); - return jobParameterMap; - } - - private void lockLoanAccounts(List loanIds, LocalDate businessDate) { - transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); - transactionTemplate.execute(new TransactionCallbackWithoutResult() { - - @Override - protected void doInTransactionWithoutResult(@NonNull TransactionStatus status) { - List loanAccountLocks = getLoanAccountLocks(loanIds, businessDate); - loanAccountLocks.forEach(loanAccountLock -> { - try { - loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING); - loanAccountLockRepository.saveAndFlush(loanAccountLock); - } catch (Exception e) { - log.error("Error updating lock on loan account. Locked loan ID: {}", loanAccountLock.getLoanId(), e); - throw new AccountLockCannotBeOverruledException( - "Error updating lock on loan account. Locked loan ID: %s".formatted(loanAccountLock.getLoanId()), e); - } - }); - } - }); - } - - private boolean isLockOverrulable(LoanAccountLock loanAccountLock) { - if (isBypassUser()) { - return true; - } else { - return StringUtils.isNotBlank(loanAccountLock.getError()); - } - } - - private boolean isBypassUser() { - return context.getAuthenticatedUserIfPresent().isBypassUser(); - } - - private void validateLoanIdsListSize(List loanIds) { - int inlineLoanCobRequestItemLimit = fineractProperties.getApi().getBodyItemSizeLimit().getInlineLoanCob(); - if (loanIds.size() > inlineLoanCobRequestItemLimit) { - String userMessage = "Size of the loan IDs list cannot be over " + inlineLoanCobRequestItemLimit; - throw new PlatformRequestBodyItemLimitValidationException(userMessage); - } + public LoanAccountLock createAccountLock(Long loanId, LockOwner loanInlineCobProcessing, LocalDate businessDate) { + return new LoanAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING, businessDate); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineWorkingCapitalLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineWorkingCapitalLoanCOBExecutorServiceImpl.java new file mode 100644 index 00000000000..d7f08085387 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineWorkingCapitalLoanCOBExecutorServiceImpl.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.service; + +import java.time.LocalDate; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.WorkingCapitalAccountLockRepository; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanRetrieveIdService; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +@Conditional(LoanCOBEnabledCondition.class) +public class InlineWorkingCapitalLoanCOBExecutorServiceImpl extends InlineCommonLockableCOBExecutorService { + + public InlineWorkingCapitalLoanCOBExecutorServiceImpl(WorkingCapitalAccountLockRepository loanAccountLockRepository, + InlineLoanCOBExecutionDataParser dataParser, JobLauncher jobLauncher, JobLocator jobLocator, JobExplorer jobExplorer, + TransactionTemplate transactionTemplate, CustomJobParameterRepository customJobParameterRepository, + PlatformSecurityContext context, WorkingCapitalLoanRetrieveIdService retrieveIdService, FineractProperties fineractProperties) { + super(loanAccountLockRepository, dataParser, jobLauncher, jobLocator, jobExplorer, transactionTemplate, + customJobParameterRepository, context, retrieveIdService, fineractProperties); + } + + @Override + public WorkingCapitalLoanAccountLock createAccountLock(Long loanId, LockOwner loanInlineCobProcessing, LocalDate businessDate) { + return new WorkingCapitalLoanAccountLock(loanId, loanInlineCobProcessing, businessDate); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java index a2bc44816c5..1e89ee8e402 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java @@ -18,16 +18,16 @@ */ package org.apache.fineract.cob.service; -import java.util.List; +import org.apache.fineract.cob.domain.AccountLockRepository; +import org.apache.fineract.cob.domain.CustomLoanAccountLockRepository; import org.apache.fineract.cob.domain.LoanAccountLock; +import org.springframework.stereotype.Service; -public interface LoanAccountLockService { +@Service +public class LoanAccountLockService extends AbstractAccountLockService { - List getLockedLoanAccountByPage(int page, int limit); - - boolean isLoanHardLocked(Long loanId); - - boolean isLockOverrulable(Long loanId); - - void updateCobAndRemoveLocks(); + public LoanAccountLockService(AccountLockRepository loanAccountLockRepository, + CustomLoanAccountLockRepository customLoanAccountLockRepository) { + super(loanAccountLockRepository, customLoanAccountLockRepository); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java index fac0c1bc758..11591ef61ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpService.java @@ -16,18 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.cob.service; - -import org.apache.fineract.cob.data.IsCatchUpRunningDTO; -import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; - -public interface LoanCOBCatchUpService { - void unlockHardLockedLoans(); - - OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan(); - - void executeLoanCOBCatchUp(); +package org.apache.fineract.cob.service; - IsCatchUpRunningDTO isCatchUpRunning(); -} +public interface LoanCOBCatchUpService extends COBCatchUpService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java index 39b346b4a3a..247565de09b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanCOBCatchUpServiceImpl.java @@ -18,61 +18,24 @@ */ package org.apache.fineract.cob.service; -import java.time.LocalDate; -import java.util.List; -import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; -import org.apache.fineract.cob.data.IsCatchUpRunningDTO; -import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO; +import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.loan.LoanCOBConstant; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.domain.FineractContext; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository; import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Service; @Service -@RequiredArgsConstructor @Conditional(LoanCOBEnabledCondition.class) -public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService { +public class LoanCOBCatchUpServiceImpl extends CommonCOBCatchUpService implements LoanCOBCatchUpService { - private final AsyncLoanCOBExecutorService asyncLoanCOBExecutorService; - private final JobExecutionRepository jobExecutionRepository; - private final RetrieveLoanIdService retrieveLoanIdService; - private final LoanAccountLockService accountLockService; - - @Override - public void unlockHardLockedLoans() { - accountLockService.updateCobAndRemoveLocks(); - } - - @Override - public OldestCOBProcessedLoanDTO getOldestCOBProcessedLoan() { - List loanIdAndLastClosedBusinessDate = retrieveLoanIdService - .retrieveLoanIdsOldestCobProcessed(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)); - OldestCOBProcessedLoanDTO oldestCOBProcessedLoanDTO = new OldestCOBProcessedLoanDTO(); - oldestCOBProcessedLoanDTO.setLoanIds(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getId).toList()); - oldestCOBProcessedLoanDTO - .setCobProcessedDate(loanIdAndLastClosedBusinessDate.stream().map(COBIdAndLastClosedBusinessDate::getLastClosedBusinessDate) - .findFirst().orElse(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE))); - oldestCOBProcessedLoanDTO.setCobBusinessDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)); - return oldestCOBProcessedLoanDTO; - } - - @Override - public void executeLoanCOBCatchUp() { - FineractContext context = ThreadLocalContextUtil.getContext(); - asyncLoanCOBExecutorService.executeLoanCOBCatchUpAsync(context); + public LoanCOBCatchUpServiceImpl(AsyncLoanCOBExecutorService asyncLoanCOBExecutorService, JobExecutionRepository jobExecutionRepository, + RetrieveLoanIdService retrieveIdService, LoanAccountLockService accountLockService) { + super(asyncLoanCOBExecutorService, jobExecutionRepository, retrieveIdService, accountLockService); } @Override - public IsCatchUpRunningDTO isCatchUpRunning() { - LocalDate runningCatchUpBusinessDate = jobExecutionRepository.getBusinessDateOfRunningJobByExecutionParameter( - LoanCOBConstant.JOB_NAME, LoanCOBConstant.COB_CUSTOM_JOB_PARAMETER_KEY, LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "true", - LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME); - return new IsCatchUpRunningDTO(runningCatchUpBusinessDate != null, runningCatchUpBusinessDate); + public String getJobName() { + return LoanCOBConstant.JOB_NAME; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpService.java new file mode 100644 index 00000000000..828648243ad --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpService.java @@ -0,0 +1,22 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.cob.service; + +public interface WorkingCapitalLoanCOBCatchUpService extends COBCatchUpService {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpServiceImpl.java new file mode 100644 index 00000000000..f0188b2a85a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/WorkingCapitalLoanCOBCatchUpServiceImpl.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.cob.service; + +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanRetrieveIdService; +import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.springframework.context.annotation.Conditional; +import org.springframework.stereotype.Service; + +@Service +@Conditional(LoanCOBEnabledCondition.class) +public class WorkingCapitalLoanCOBCatchUpServiceImpl extends CommonCOBCatchUpService + implements WorkingCapitalLoanCOBCatchUpService { + + public WorkingCapitalLoanCOBCatchUpServiceImpl(AsyncWorkingCapitalLoanCOBExecutorService asyncLoanCOBExecutorService, + JobExecutionRepository jobExecutionRepository, WorkingCapitalLoanRetrieveIdService retrieveIdService, + AccountLockService accountLockService) { + super(asyncLoanCOBExecutorService, jobExecutionRepository, retrieveIdService, accountLockService); + } + + @Override + public String getJobName() { + return JobName.WORKING_CAPITAL_LOAN_COB_JOB.name(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java index 308ee6cee7e..03bdae55d97 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/accountnumberformat/service/AccountNumberFormatReadPlatformServiceImpl.java @@ -54,20 +54,14 @@ public class AccountNumberFormatReadPlatformServiceImpl implements AccountNumber private static final class AccountNumberFormatMapper implements RowMapper { - private final String schema; + private static final String ACCOUNT_NUMBER_FORMAT_SCHEMA = """ + anf.id as id, anf.account_type_enum as accountTypeEnum, anf.prefix_type_enum as prefixTypeEnum, anf.prefix_character as prefixCharacter + from c_account_number_format anf\s"""; - AccountNumberFormatMapper() { - final StringBuilder builder = new StringBuilder(400); - - builder.append( - " anf.id as id, anf.account_type_enum as accountTypeEnum, anf.prefix_type_enum as prefixTypeEnum, anf.prefix_character as prefixCharacter"); - builder.append(" from c_account_number_format anf "); - - this.schema = builder.toString(); - } + AccountNumberFormatMapper() {} public String schema() { - return this.schema; + return ACCOUNT_NUMBER_FORMAT_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java index a93f674073c..c7226e1c771 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/constants/TemplatePopulateImportConstants.java @@ -141,6 +141,7 @@ private TemplatePopulateImportConstants() { // Guarantor Types public static final String GUARANTOR_INTERNAL = "Internal"; public static final String GUARANTOR_EXTERNAL = "External"; + public static final String GUARANTOR_GROUP = "Group"; // Loan Account/Loan repayment Client External Id public static final Boolean CONTAINS_CLIENT_EXTERNAL_ID = true; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java index e8b8c8c8b62..c51be9c6382 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/guarantor/GuarantorImportHandler.java @@ -94,6 +94,8 @@ private GuarantorData readGuarantor(final Workbook workbook, final Row row, Long guarantorTypeId = 1; } else if (guarantorType.equalsIgnoreCase(TemplatePopulateImportConstants.GUARANTOR_EXTERNAL)) { guarantorTypeId = 3; + } else if (guarantorType.equalsIgnoreCase(TemplatePopulateImportConstants.GUARANTOR_GROUP)) { + guarantorTypeId = 4; } } String clientName = ImportHandlerUtils.readAsString(GuarantorConstants.ENTITY_ID_COL, row); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/guarantor/GuarantorWorkbookPopulator.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/guarantor/GuarantorWorkbookPopulator.java index 984bee37d1c..ab9326b68a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/guarantor/GuarantorWorkbookPopulator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/populator/guarantor/GuarantorWorkbookPopulator.java @@ -209,8 +209,9 @@ private void setRules(Sheet worksheet) { "INDIRECT(CONCATENATE(\"Account_\",SUBSTITUTE(SUBSTITUTE(SUBSTITUTE($B1,\" \",\"_\"),\"(\",\"_\"),\")\",\"_\")))"); DataValidationConstraint savingsaccountNumberConstraint = validationHelper.createFormulaListConstraint( "INDIRECT(CONCATENATE(\"SavingsAccount_\",SUBSTITUTE(SUBSTITUTE(SUBSTITUTE($G1,\" \",\"_\"),\"(\",\"_\"),\")\",\"_\")))"); - DataValidationConstraint guranterTypeConstraint = validationHelper.createExplicitListConstraint( - new String[] { TemplatePopulateImportConstants.GUARANTOR_INTERNAL, TemplatePopulateImportConstants.GUARANTOR_EXTERNAL }); + DataValidationConstraint guranterTypeConstraint = validationHelper + .createExplicitListConstraint(new String[] { TemplatePopulateImportConstants.GUARANTOR_INTERNAL, + TemplatePopulateImportConstants.GUARANTOR_EXTERNAL, TemplatePopulateImportConstants.GUARANTOR_GROUP }); DataValidationConstraint guarantorRelationshipConstraint = validationHelper.createFormulaListConstraint("GuarantorRelationship"); DataValidationConstraint entityofficeNameConstraint = validationHelper.createFormulaListConstraint("Office"); DataValidationConstraint entityclientNameConstraint = validationHelper diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java index 78cf8d35cd0..221115c176d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportWorkbookServiceImpl.java @@ -227,13 +227,14 @@ public ImportData getImport(Long id) { private static final class ImportMapper implements RowMapper { + private static final String IMPORT_SCHEMA = """ + i.id as id, i.document_id as documentId, d.name as name, i.import_time as importTime, i.end_time as endTime, + i.completed as completed, i.total_records as totalRecords, i.success_count as successCount, + i.failure_count as failureCount, i.createdby_id as createdBy + from m_import_document i inner join m_document d on i.document_id=d.id where i.entity_type= ?\s"""; + public String schema() { - final StringBuilder sql = new StringBuilder(); - sql.append("i.id as id, i.document_id as documentId, d.name as name, i.import_time as importTime, i.end_time as endTime, ") - .append("i.completed as completed, i.total_records as totalRecords, i.success_count as successCount, ") - .append("i.failure_count as failureCount, i.createdby_id as createdBy ") - .append("from m_import_document i inner join m_document d on i.document_id=d.id ").append("where i.entity_type= ? "); - return sql.toString(); + return IMPORT_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java index ce0aee75c67..8067ea08acd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailCampaignReadPlatformServiceImpl.java @@ -63,45 +63,41 @@ public EmailCampaignReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { private static final class EmailCampaignMapper implements RowMapper { - final String schema; - - private EmailCampaignMapper() { - final StringBuilder sql = new StringBuilder(400); - sql.append("ec.id as id, "); - sql.append("ec.campaign_name as campaignName, "); - sql.append("ec.campaign_type as campaignType, "); - sql.append("ec.business_rule_id as businessRuleId, "); - sql.append("ec.email_subject as emailSubject, "); - sql.append("ec.email_message as emailMessage, "); - sql.append("ec.email_attachment_file_format as emailAttachmentFileFormat, "); - sql.append("sr.id as stretchyReportId, "); - sql.append("sr.report_name as reportName, sr.report_type as reportType, sr.report_subtype as reportSubType, "); - sql.append("sr.report_category as reportCategory, sr.report_sql as reportSql, sr.description as reportDescription, "); - sql.append("sr.core_report as coreReport, sr.use_report as useReport, "); - sql.append("ec.stretchy_report_param_map as stretchyReportParamMap, "); - sql.append("ec.param_value as paramValue, "); - sql.append("ec.status_enum as statusEnum, "); - sql.append("ec.recurrence as recurrence, "); - sql.append("ec.recurrence_start_date as recurrenceStartDate, "); - sql.append("ec.next_trigger_date as nextTriggerDate, "); - sql.append("ec.last_trigger_date as lastTriggerDate, "); - sql.append("ec.submittedon_date as submittedOnDate, "); - sql.append("sbu.username as submittedByUsername, "); - sql.append("ec.closedon_date as closedOnDate, "); - sql.append("clu.username as closedByUsername, "); - sql.append("acu.username as activatedByUsername, "); - sql.append("ec.approvedon_date as activatedOnDate "); - sql.append("from scheduled_email_campaign ec "); - sql.append("left join m_appuser sbu on sbu.id = ec.submittedon_userid "); - sql.append("left join m_appuser acu on acu.id = ec.approvedon_userid "); - sql.append("left join m_appuser clu on clu.id = ec.closedon_userid "); - sql.append("left join stretchy_report sr on ec.stretchy_report_id = sr.id"); - - this.schema = sql.toString(); - } + private static final String EMAIL_CAMPAIGN_SCHEMA = """ + ec.id as id, + ec.campaign_name as campaignName, + ec.campaign_type as campaignType, + ec.business_rule_id as businessRuleId, + ec.email_subject as emailSubject, + ec.email_message as emailMessage, + ec.email_attachment_file_format as emailAttachmentFileFormat, + sr.id as stretchyReportId, + sr.report_name as reportName, sr.report_type as reportType, sr.report_subtype as reportSubType, + sr.report_category as reportCategory, sr.report_sql as reportSql, sr.description as reportDescription, + sr.core_report as coreReport, sr.use_report as useReport, + ec.stretchy_report_param_map as stretchyReportParamMap, + ec.param_value as paramValue, + ec.status_enum as statusEnum, + ec.recurrence as recurrence, + ec.recurrence_start_date as recurrenceStartDate, + ec.next_trigger_date as nextTriggerDate, + ec.last_trigger_date as lastTriggerDate, + ec.submittedon_date as submittedOnDate, + sbu.username as submittedByUsername, + ec.closedon_date as closedOnDate, + clu.username as closedByUsername, + acu.username as activatedByUsername, + ec.approvedon_date as activatedOnDate + from scheduled_email_campaign ec + left join m_appuser sbu on sbu.id = ec.submittedon_userid + left join m_appuser acu on acu.id = ec.approvedon_userid + left join m_appuser clu on clu.id = ec.closedon_userid + left join stretchy_report sr on ec.stretchy_report_id = sr.id\s"""; + + private EmailCampaignMapper() {} public String schema() { - return this.schema; + return EMAIL_CAMPAIGN_SCHEMA; } @Override @@ -144,28 +140,24 @@ public EmailCampaignData mapRow(ResultSet rs, int rowNum) throws SQLException { private static final class BusinessRuleMapper implements ResultSetExtractor> { - final String schema; - - private BusinessRuleMapper() { - final StringBuilder sql = new StringBuilder(300); - sql.append("sr.id as id, "); - sql.append("sr.report_name as reportName, "); - sql.append("sr.report_type as reportType, "); - sql.append("sr.report_subtype as reportSubType, "); - sql.append("sr.description as description, "); - sql.append("sp.parameter_variable as params, "); - sql.append("sp.parameter_FormatType as paramType, "); - sql.append("sp.parameter_label as paramLabel, "); - sql.append("sp.parameter_name as paramName "); - sql.append("from stretchy_report sr "); - sql.append("left join stretchy_report_parameter as srp on srp.report_id = sr.id "); - sql.append("left join stretchy_parameter as sp on sp.id = srp.parameter_id "); - - this.schema = sql.toString(); - } + private static final String BUSINESS_RULE_SCHEMA = """ + sr.id as id, + sr.report_name as reportName, + sr.report_type as reportType, + sr.report_subtype as reportSubType, + sr.description as description, + sp.parameter_variable as params, + sp.parameter_FormatType as paramType, + sp.parameter_label as paramLabel, + sp.parameter_name as paramName + from stretchy_report sr + left join stretchy_report_parameter as srp on srp.report_id = sr.id + left join stretchy_parameter as sp on sp.id = srp.parameter_id\s"""; + + private BusinessRuleMapper() {} public String schema() { - return this.schema; + return BUSINESS_RULE_SCHEMA; } @Override @@ -238,7 +230,7 @@ public EmailBusinessRulesData retrieveOneTemplate(Long resourceId) { public EmailCampaignData retrieveOne(Long resourceId) { final boolean isVisible = true; try { - final String sql = "select " + this.emailCampaignMapper.schema + " where ec.id = ? and ec.is_visible = ?"; + final String sql = "select " + this.emailCampaignMapper.schema() + " where ec.id = ? and ec.is_visible = ?"; return this.jdbcTemplate.queryForObject(sql, this.emailCampaignMapper, new Object[] { resourceId, isVisible }); // NOSONAR } catch (final EmptyResultDataAccessException e) { throw new EmailCampaignNotFound(resourceId, e); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java index 2c274607db9..4b30d7eaa9a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailConfigurationReadPlatformServiceImpl.java @@ -46,20 +46,16 @@ public EmailConfigurationReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate private static final class EmailConfigurationRowMapper implements RowMapper { - final String schema; + private static final String EMAIL_CONFIGURATION_SCHEMA = """ + cnf.id as id, + cnf.name as name, + cnf.value as value + from scheduled_email_configuration cnf\s"""; - EmailConfigurationRowMapper() { - final StringBuilder sql = new StringBuilder(300); - sql.append("cnf.id as id, "); - sql.append("cnf.name as name, "); - sql.append("cnf.value as value "); - sql.append("from scheduled_email_configuration cnf"); - - this.schema = sql.toString(); - } + EmailConfigurationRowMapper() {} public String schema() { - return this.schema; + return EMAIL_CONFIGURATION_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java index 955ca561f68..e52bc00b454 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/email/service/EmailReadPlatformServiceImpl.java @@ -52,28 +52,24 @@ public class EmailReadPlatformServiceImpl implements EmailReadPlatformService { private static final class EmailMapper implements RowMapper { - final String schema; - - EmailMapper() { - final StringBuilder sql = new StringBuilder(300); - sql.append(" emo.id as id, "); - sql.append("emo.group_id as groupId, "); - sql.append("emo.client_id as clientId, "); - sql.append("emo.staff_id as staffId, "); - sql.append("emo.campaign_name as campaignName, "); - sql.append("emo.status_enum as statusId, "); - sql.append("emo.email_address as emailAddress, "); - sql.append("emo.submittedon_date as sentDate, "); - sql.append("emo.email_subject as emailSubject, "); - sql.append("emo.message as message, "); - sql.append("emo.error_message as errorMessage "); - sql.append("from " + tableName() + " emo"); - - this.schema = sql.toString(); - } + private static final String EMAIL_SCHEMA = """ + emo.id as id, + emo.group_id as groupId, + emo.client_id as clientId, + emo.staff_id as staffId, + emo.campaign_name as campaignName, + emo.status_enum as statusId, + emo.email_address as emailAddress, + emo.submittedon_date as sentDate, + emo.email_subject as emailSubject, + emo.message as message, + emo.error_message as errorMessage + from scheduled_email_messages_outbound emo\s"""; + + EmailMapper() {} public String schema() { - return this.schema; + return EMAIL_SCHEMA; } public String tableName() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java index a36d43c12b3..6c06210b83b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java @@ -37,4 +37,8 @@ public interface SmsCampaignRepository extends JpaRepository, @Query("SELECT campaign FROM SmsCampaign campaign WHERE campaign.paramValue LIKE :reportPattern AND campaign.triggerType=:triggerType AND campaign.status=300") List findActiveSmsCampaigns(@Param("reportPattern") String reportPattern, @Param("triggerType") Integer triggerType); + + boolean existsByCampaignName(String campaignName); + + boolean existsByCampaignNameAndIdNot(String campaignName, Long id); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java new file mode 100644 index 00000000000..6bc7e40dbcf --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.campaigns.sms.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class SmsCampaignNameAlreadyExistsException extends AbstractPlatformDomainRuleException { + + public SmsCampaignNameAlreadyExistsException(final String name) { + super("error.msg.sms.campaign.duplicate.name", "An SMS campaign with name '" + name + "' already exists", name); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/mapper/SmsCampaignMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/mapper/SmsCampaignMapper.java index 588c96de107..e6bff53053c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/mapper/SmsCampaignMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/mapper/SmsCampaignMapper.java @@ -48,34 +48,33 @@ public String schema() { } private String buildCampaignColumn() { - final StringBuilder sql = new StringBuilder(400); - sql.append("sc.id as id, "); - sql.append("sc.campaign_name as campaignName, "); - sql.append("sc.campaign_type as campaignType, "); - sql.append("sc.campaign_trigger_type as triggerType, "); - sql.append("sc.report_id as runReportId, "); - sql.append("sc.message as message, "); - sql.append("sc.param_value as paramValue, "); - sql.append("sc.status_enum as status, "); - sql.append("sc.recurrence as recurrence, "); - sql.append("sc.recurrence_start_date as recurrenceStartDate, "); - sql.append("sc.next_trigger_date as nextTriggerDate, "); - sql.append("sc.last_trigger_date as lastTriggerDate, "); - sql.append("sc.submittedon_date as submittedOnDate, "); - sql.append("sbu.username as submittedByUsername, "); - sql.append("sc.closedon_date as closedOnDate, "); - sql.append("clu.username as closedByUsername, "); - sql.append("acu.username as activatedByUsername, "); - sql.append("sc.approvedon_date as activatedOnDate, "); - sql.append("sr.report_name as reportName, "); - sql.append("provider_id as providerId, "); - sql.append("sc.is_notification as isNotification "); - sql.append("from sms_campaign sc "); - sql.append("left join m_appuser sbu on sbu.id = sc.submittedon_userid "); - sql.append("left join m_appuser acu on acu.id = sc.approvedon_userid "); - sql.append("left join m_appuser clu on clu.id = sc.closedon_userid "); - sql.append("left join stretchy_report sr on sr.id = sc.report_id "); - return sql.toString(); + return """ + sc.id as id, + sc.campaign_name as campaignName, + sc.campaign_type as campaignType, + sc.campaign_trigger_type as triggerType, + sc.report_id as runReportId, + sc.message as message, + sc.param_value as paramValue, + sc.status_enum as status, + sc.recurrence as recurrence, + sc.recurrence_start_date as recurrenceStartDate, + sc.next_trigger_date as nextTriggerDate, + sc.last_trigger_date as lastTriggerDate, + sc.submittedon_date as submittedOnDate, + sbu.username as submittedByUsername, + sc.closedon_date as closedOnDate, + clu.username as closedByUsername, + acu.username as activatedByUsername, + sc.approvedon_date as activatedOnDate, + sr.report_name as reportName, + provider_id as providerId, + sc.is_notification as isNotification + from sms_campaign sc + left join m_appuser sbu on sbu.id = sc.submittedon_userid + left join m_appuser acu on acu.id = sc.approvedon_userid + left join m_appuser clu on clu.id = sc.closedon_userid + left join stretchy_report sr on sr.id = sc.report_id\s"""; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java index db4b9e82eac..ecbf6d56c96 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java @@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToBeDeletedException; import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToEditException; +import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNameAlreadyExistsException; import org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound; import org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -104,20 +105,32 @@ public class SmsCampaignWritePlatformServiceJpaImpl implements SmsCampaignWriteP @Transactional @Override public CommandProcessingResult create(JsonCommand command) { - final AppUser currentUser = this.context.authenticatedUser(); - this.smsCampaignValidator.validateCreate(command.json()); - final Long runReportId = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); - Report report = this.reportRepository.findById(runReportId).orElseThrow(() -> new ReportNotFoundException(runReportId)); - LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); - SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, command); - LocalDateTime recurrenceStartDate = smsCampaign.getRecurrenceStartDate(); - if (recurrenceStartDate != null && DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { - throw new GeneralPlatformDomainRuleException("error.msg.campaign.recurrenceStartDate.in.the.past", - "Recurrence start date cannot be the past date.", recurrenceStartDate); - } - this.smsCampaignRepository.saveAndFlush(smsCampaign); + try { + final AppUser currentUser = this.context.authenticatedUser(); + this.smsCampaignValidator.validateCreate(command.json()); - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build(); + final String campaignName = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName); + if (this.smsCampaignRepository.existsByCampaignName(campaignName)) { + throw new SmsCampaignNameAlreadyExistsException(campaignName); + } + + final Long runReportId = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); + Report report = this.reportRepository.findById(runReportId).orElseThrow(() -> new ReportNotFoundException(runReportId)); + LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant(); + SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, command); + LocalDateTime recurrenceStartDate = smsCampaign.getRecurrenceStartDate(); + if (recurrenceStartDate != null && DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) { + throw new GeneralPlatformDomainRuleException("error.msg.campaign.recurrenceStartDate.in.the.past", + "Recurrence start date cannot be the past date.", recurrenceStartDate); + } + this.smsCampaignRepository.saveAndFlush(smsCampaign); + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build(); + } catch (final JpaSystemException | DataIntegrityViolationException dve) { + final Throwable throwable = dve.getMostSpecificCause(); + handleDataIntegrityIssues(command, throwable); + return CommandProcessingResult.empty(); + } } @Transactional @@ -135,6 +148,13 @@ public CommandProcessingResult update(final Long resourceId, final JsonCommand c } final Map changes = smsCampaign.update(command); + if (changes.containsKey(SmsCampaignValidator.campaignName)) { + final String newName = (String) changes.get(SmsCampaignValidator.campaignName); + if (this.smsCampaignRepository.existsByCampaignNameAndIdNot(newName, resourceId)) { + throw new SmsCampaignNameAlreadyExistsException(newName); + } + } + if (changes.containsKey(SmsCampaignValidator.runReportId)) { final Long newValue = command.longValueOfParameterNamed(SmsCampaignValidator.runReportId); final Report reportId = this.reportRepository.findById(newValue).orElseThrow(() -> new ReportNotFoundException(newValue)); @@ -538,6 +558,10 @@ public CommandProcessingResult reactivateSmsCampaign(final Long campaignId, Json } private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause) { + if (realCause.getMessage().contains("campaign_name_UNIQUE")) { + final String name = command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName); + throw new SmsCampaignNameAlreadyExistsException(name); + } throw ErrorHandler.getMappable(realCause, "error.msg.sms.campaign.unknown.data.integrity.issue", "Unknown data integrity issue with resource: " + realCause.getMessage()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java index 3a2e855cf58..a05348d1a51 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResource.java @@ -176,8 +176,8 @@ public String deleteCodeValue(@PathParam("codeId") @Parameter(description = "cod @Path("name/{codeName}/codevalues") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Code Values", description = "Returns the list of Code Values for a given Code\n" + "\n" - + "Example Requests:\n" + "\n" + "codes/1/codevalues", parameters = @Parameter(name = "codeId", description = "co")) + @Operation(summary = "List Code Values", operationId = "retrieveAllCodeValuesByCodeName", description = "Returns the list of Code Values for a given Code\n" + + "\n" + "Example Requests:\n" + "\n" + "codes/1/codevalues", parameters = @Parameter(name = "codeId", description = "co")) @ApiResponse(responseCode = "200", description = "A List of code values for a given code", content = @Content(array = @ArraySchema(schema = @Schema(implementation = CodeValuesApiResourceSwagger.GetCodeValuesDataResponse.class)))) public List retrieveAllCodeValues(@Context final UriInfo uriInfo, @PathParam("codeName") @Parameter(description = "codeName") final String codeName) { @@ -207,7 +207,7 @@ public CodeValueData retrieveCodeValue(@Context final UriInfo uriInfo, @Path("name/{codeName}/codevalues") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Code description", description = "") + @Operation(summary = "Create a Code description", operationId = "createCodeValueByCodeName", description = "") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = CodeValuesApiResourceSwagger.PostCodeValuesDataRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CodeValuesApiResourceSwagger.PostCodeValueDataResponse.class))) public CommandProcessingResult createCodeValue(@PathParam("codeName") @Parameter(description = "codeName") final String codeName, @@ -223,7 +223,7 @@ public CommandProcessingResult createCodeValue(@PathParam("codeName") @Parameter @Path("name/{codeName}/codevalues/{codeValueId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Code description", description = "Updates the details of a code description.") + @Operation(summary = "Update a Code description", operationId = "updateCodeValueByCodeName", description = "Updates the details of a code description.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = CodeValuesApiResourceSwagger.PutCodeValuesDataRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = CodeValuesApiResourceSwagger.PutCodeValueDataResponse.class))) public CommandProcessingResult updateCodeValue(@PathParam("codeName") @Parameter(description = "codeName") final String codeName, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java index 2903a37bcc1..4095ea1f430 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/GlobalConfigurationApiResource.java @@ -101,8 +101,8 @@ public String retrieveConfiguration(@Context final UriInfo uriInfo, @Path("{configId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Global Configuration", description = "Returns a global enable/disable configurations.\n" + "\n" - + "Example Requests:\n" + "\n" + "configurations/1") + @Operation(summary = "Retrieve Global Configuration", operationId = "retrieveOneGlobalConfiguration", description = "Returns a global enable/disable configurations.\n" + + "\n" + "Example Requests:\n" + "\n" + "configurations/1") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GlobalConfigurationPropertyData.class))) public String retrieveOne(@PathParam("configId") @Parameter(description = "configId") final Long configId, @Context final UriInfo uriInfo) { @@ -136,7 +136,7 @@ public String retrieveOneByName(@PathParam("name") @Parameter(description = "nam @Path("{configId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update Global Configuration", description = "Updates an enable/disable global configuration item.") + @Operation(summary = "Update Global Configuration", operationId = "updateGlobalConfiguration", description = "Updates an enable/disable global configuration item.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = GlobalConfigurationApiResourceSwagger.PutGlobalConfigurationsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GlobalConfigurationApiResourceSwagger.PutGlobalConfigurationsResponse.class))) public String updateConfiguration(@PathParam("configId") @Parameter(description = "configId") final Long configId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/InternalConfigurationsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/InternalConfigurationsApiResource.java index cec0e3fec89..bd1ca019765 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/InternalConfigurationsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/api/InternalConfigurationsApiResource.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.configuration.api; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.swagger.v3.oas.annotations.Operation; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; @@ -65,6 +66,7 @@ public void afterPropertiesSet() throws Exception { @Path("name/{configName}/value/{configValue}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update internal global configuration", operationId = "updateInternalGlobalConfiguration") @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") public Response updateGlobalConfiguration(@PathParam("configName") String configName, @PathParam("configValue") Long configValue) { log.warn("------------------------------------------------------------"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java index 12c1b0a9b31..3493b3e90c3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/async/SpringAsyncConfig.java @@ -37,6 +37,13 @@ public ThreadPoolTaskExecutor loanCOBCatchUpThreadPoolTaskExecutor() { return threadPoolTaskExecutor; } + @Bean(name = TaskExecutorConstant.WORKING_CAPITAL_LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME) + public ThreadPoolTaskExecutor workingCapitalLoanCOBCatchUpThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); + threadPoolTaskExecutor.setMaxPoolSize(1); + return threadPoolTaskExecutor; + } + @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 94695f2f203..42838c3064b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -549,6 +549,16 @@ public String getAssetOwnerTransferOustandingInterestStrategy() { GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY).getStringValue(); } + @Override + public boolean isForceWithdrawalOnSavingsAccountEnabled() { + return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT).isEnabled(); + } + + @Override + public Long retrieveForceWithdrawalOnSavingsAccountLimit() { + return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT).getValue(); + } + @Override public Integer getPasswordReuseRestrictionCount() { final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData( @@ -559,4 +569,9 @@ public Integer getPasswordReuseRestrictionCount() { Long value = property.getValue(); return value != null && value > 0 ? value.intValue() : 0; } + + @Override + public boolean isForcePasswordResetOnFirstLoginEnabled() { + return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.FORCE_PASSWORD_RESET_ON_FIRST_LOGIN).isEnabled(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java index ca17edbd7a6..2aa14cb57e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java @@ -45,6 +45,7 @@ import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter; import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter; import org.apache.fineract.infrastructure.security.service.AuthTenantDetailsService; +import org.apache.fineract.infrastructure.security.service.PlatformUserDetailsChecker; import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService; import org.apache.fineract.infrastructure.security.service.TwoFactorService; import org.apache.fineract.notification.service.UserNotificationService; @@ -115,7 +116,9 @@ public class SecurityConfig { @Autowired private IdempotencyStoreHelper idempotencyStoreHelper; @Autowired - ProgressiveLoanModelCheckerFilter progressiveLoanModelCheckerFilter; + private ProgressiveLoanModelCheckerFilter progressiveLoanModelCheckerFilter; + @Autowired + private PlatformUserDetailsChecker platformUserDetailsChecker; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -297,6 +300,41 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_PAYMENTTYPE") .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/paymenttypes")) .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_PAYMENTTYPE") + // mix: taxonomy + .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/mixtaxonomy/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_MIX_TAXONOMY") + .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/mixtaxonomy/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_MIX_TAXONOMY") + .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/mixtaxonomy/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_MIX_TAXONOMY") + .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/mixtaxonomy/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_MIX_TAXONOMY") + // mix: mapping + .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/mixmapping/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_MIX_MAPPING") + .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/mixmapping/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_MIX_MAPPING") + .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/mixmapping/*")) + // TODO: "UPDATE_XBRLMAPPING" is the legacy permission name; we should rename for consistency + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_MIX_MAPPING", "UPDATE_XBRLMAPPING") + .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/mixmapping/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_MIX_MAPPING") + // mix: report + .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/mixreport/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_MIX_REPORT") + .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/mixreport/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "CREATE_MIX_REPORT") + .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/mixreport/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_MIX_REPORT") + .requestMatchers(API_MATCHER.matcher(HttpMethod.DELETE, "/api/*/mixreport/*")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "DELETE_MIX_REPORT") + // working days + .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/workingdays")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKING_DAYS") + .requestMatchers(API_MATCHER.matcher(HttpMethod.GET, "/api/*/workingdays/template")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_READ, "READ_WORKING_DAYS") + .requestMatchers(API_MATCHER.matcher(HttpMethod.PUT, "/api/*/workingdays")) + .hasAnyAuthority(ALL_FUNCTIONS, ALL_FUNCTIONS_WRITE, "UPDATE_WORKING_DAYS") .requestMatchers(API_MATCHER.matcher(HttpMethod.POST, "/api/*/twofactor/validate")).fullyAuthenticated() .requestMatchers(API_MATCHER.matcher("/api/*/twofactor")).fullyAuthenticated() @@ -390,6 +428,7 @@ public DaoAuthenticationProvider authProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); + authProvider.setPostAuthenticationChecks(platformUserDetailsChecker); return authProvider; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java index f4ae4a5e482..7f0db741bcd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SpringConfig.java @@ -19,34 +19,68 @@ package org.apache.fineract.infrastructure.core.config; +import java.util.concurrent.ThreadPoolExecutor; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; +import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.event.SimpleApplicationEventMulticaster; -import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor; @Configuration public class SpringConfig { + private static final int AWAIT_TERMINATION_SECONDS = 60; + private static final int DEFAULT_QUEUE_CAPACITY = 100; + + @Bean(name = "fineractEventExecutor") + public ThreadPoolTaskExecutor fineractEventExecutor(ThreadPoolTaskExecutorBuilder builder, + @Value("${spring.task.execution.pool.core-size:-1}") int configuredCore, + @Value("${spring.task.execution.pool.max-size:-1}") int configuredMax, + @Value("${spring.task.execution.pool.queue-capacity:-1}") int configuredQueueCapacity) { + + int cpus = Runtime.getRuntime().availableProcessors(); + int smartCore = cpus * 2; + int smartMax = cpus * 5; + + int coreSize = configuredCore > 0 ? configuredCore : smartCore; + int rawMaxSize = configuredMax > 0 ? configuredMax : smartMax; + int queueCapacity = configuredQueueCapacity >= 0 ? configuredQueueCapacity : DEFAULT_QUEUE_CAPACITY; + + int finalMaxSize = Math.max(rawMaxSize, coreSize); + + ThreadPoolTaskExecutor executor = builder.threadNamePrefix("FineractEvent-").corePoolSize(coreSize).maxPoolSize(finalMaxSize) + .queueCapacity(queueCapacity).build(); + + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(AWAIT_TERMINATION_SECONDS); + + return executor; + } + @Bean - public SimpleApplicationEventMulticaster applicationEventMulticaster() { - SimpleApplicationEventMulticaster saem = new SimpleApplicationEventMulticaster(); - saem.setTaskExecutor(new SimpleAsyncTaskExecutor()); - return saem; + @DependsOn("overrideSecurityContextHolderStrategy") + public SimpleApplicationEventMulticaster applicationEventMulticaster( + @Qualifier("fineractEventExecutor") ThreadPoolTaskExecutor taskExecutor) { + SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); + multicaster.setTaskExecutor(new DelegatingSecurityContextAsyncTaskExecutor(taskExecutor)); + return multicaster; } - // The application events (for importing) rely on the inheritable thread local security context strategy - // This is NOT compatible with threadpools so if we use threadpools the below will need to be reworked @Bean public MethodInvokingFactoryBean overrideSecurityContextHolderStrategy() { - MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean(); - mifb.setTargetClass(SecurityContextHolder.class); - mifb.setTargetMethod("setStrategyName"); - mifb.setArguments("MODE_INHERITABLETHREADLOCAL"); - return mifb; + MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean(); + factoryBean.setTargetClass(SecurityContextHolder.class); + factoryBean.setTargetMethod("setStrategyName"); + factoryBean.setArguments(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); + return factoryBean; } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TaskExecutorConstant.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TaskExecutorConstant.java index 59c0668242e..1aeeddb2510 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TaskExecutorConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/TaskExecutorConstant.java @@ -28,4 +28,5 @@ private TaskExecutorConstant() { public static final String CONFIGURABLE_TASK_EXECUTOR_BEAN_NAME = "fineractConfigurableThreadPoolTaskExecutor"; public static final String EVENT_TASK_EXECUTOR_BEAN_NAME = "externalEventJmsProducerExecutor"; public static final String LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME = "loanCOBCatchUpThreadPoolTaskExecutor"; + public static final String WORKING_CAPITAL_LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME = "workingCapitalLoanCOBCatchUpThreadPoolTaskExecutor"; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationApiResource.java index 528c1b1b4f3..7914b8e2dcc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/api/CreditBureauIntegrationApiResource.java @@ -24,6 +24,7 @@ import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; @@ -64,6 +65,7 @@ @Path("/v1/creditBureauIntegration") @Component +@Tag(name = "Credit Bureau Integration", description = "") @RequiredArgsConstructor public class CreditBureauIntegrationApiResource { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableRejectionCleanupService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableRejectionCleanupService.java new file mode 100644 index 00000000000..c49fc6f2666 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableRejectionCleanupService.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.dataqueries.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.commands.domain.CommandSource; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class DatatableRejectionCleanupService implements CleanupService { + + private final JdbcTemplate jdbcTemplate; + private final DatabaseSpecificSQLGenerator sqlGenerator; + private final FromJsonHelper fromJsonHelper; + + @Override + public void cleanup(CommandSource commandSource) { + + boolean isCreateAction = "CREATE".equals(commandSource.getActionName()); + boolean isDatatableEntity = "DATATABLE".equals(commandSource.getEntityName()); + if (!isCreateAction || !isDatatableEntity) { + return; + } + + final String datatableName = fromJsonHelper.parse(commandSource.getCommandAsJson()).getAsJsonObject().get("datatableName") + .getAsString(); + + final String sql = "DROP TABLE IF EXISTS " + sqlGenerator.escape(datatableName); + log.info("Cleaning up orphaned datatable after rejection: {}", datatableName); + jdbcTemplate.execute(sql); + + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java index 5c07474ea5e..3f81b76674e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java @@ -277,7 +277,7 @@ public CommandProcessingResult createDatatable(final JsonCommand command) { sqlBuilder.append(constrainBuilder); sqlBuilder.append(")"); if (databaseTypeResolver.isMySQL()) { - sqlBuilder.append(" ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;"); + sqlBuilder.append(" ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_UNICODE_CI;"); } log.debug("SQL:: {}", sqlBuilder); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java index 22c1c0b3c5a..767a768e2f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/entityaccess/service/FineractEntityAccessReadServiceImpl.java @@ -135,13 +135,14 @@ public Collection retrieveEntityAccessFor(Fin @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT") private String getSQLForRetriveEntityAccessFor() { - StringBuilder str = new StringBuilder("select eem.rel_id as relId,eem.from_id as fromId, "); - str.append("eem.to_id as toId, eem.start_date as startDate, eem.end_date as endDate "); - str.append("from m_entity_to_entity_mapping eem "); - str.append("where eem.rel_id = ? "); - str.append("and eem.from_id = ? "); - LOG.debug("{}", str); - return str.toString(); + final String sql = """ + select eem.rel_id as relId,eem.from_id as fromId, + eem.to_id as toId, eem.start_date as startDate, eem.end_date as endDate + from m_entity_to_entity_mapping eem + where eem.rel_id = ? + and eem.from_id = ?\s"""; + LOG.debug("{}", sql); + return sql; } @Override @@ -186,10 +187,10 @@ public Collection retrieveAllSupportedMappingTypes() private static final class EntityRelationMapper implements RowMapper { - private final StringBuilder sqlBuilder = new StringBuilder("select id as id,code_name as mapping_Types from m_entity_relation "); + private static final String ENTITY_RELATION_SCHEMA = "select id as id,code_name as mapping_Types from m_entity_relation "; public String schema() { - return this.sqlBuilder.toString(); + return ENTITY_RELATION_SCHEMA; } @Override @@ -221,19 +222,16 @@ public Collection retrieveOneMapping(Long map private static final class GetOneEntityMapper implements RowMapper { - private final String schema; + private static final String GET_ONE_ENTITY_SCHEMA = """ + select eem.rel_id as relId, + eem.from_id as fromId,eem.to_Id as toId,eem.start_date as startDate,eem.end_date as endDate + from m_entity_to_entity_mapping eem + where eem.id= ?\s"""; - GetOneEntityMapper() { - - StringBuilder str = new StringBuilder("select eem.rel_id as relId, "); - str.append("eem.from_id as fromId,eem.to_Id as toId,eem.start_date as startDate,eem.end_date as endDate "); - str.append("from m_entity_to_entity_mapping eem "); - str.append("where eem.id= ? "); - this.schema = str.toString(); - } + GetOneEntityMapper() {} public String schema() { - return this.schema; + return GET_ONE_ENTITY_SCHEMA; } @Override @@ -254,58 +252,54 @@ public FineractEntityToEntityMappingData mapRow(final ResultSet rs, @SuppressWar private static final class EntityToEntityMapper implements RowMapper { - private final String schema; - - EntityToEntityMapper() { - - StringBuilder str = new StringBuilder("select eem.id as mapId, "); - str.append("eem.rel_id as relId, "); - str.append("eem.from_id as from_id, "); - str.append("eem.to_id as to_id, "); - str.append("eem.start_date as startDate, "); - str.append("eem.end_date as endDate, "); - str.append("case er.code_name "); - str.append("when 'office_access_to_loan_products' then "); - str.append("o.name "); - str.append("when 'office_access_to_savings_products' then "); - str.append("o.name "); - str.append("when 'office_access_to_fees/charges' then "); - str.append("o.name "); - str.append("when 'role_access_to_loan_products' then "); - str.append("r.name "); - str.append("when 'role_access_to_savings_products' then "); - str.append("r.name "); - str.append("end as from_name, "); - str.append("case er.code_name "); - str.append("when 'office_access_to_loan_products' then "); - str.append("lp.name "); - str.append("when 'office_access_to_savings_products' then "); - str.append("sp.name "); - str.append("when 'office_access_to_fees/charges' then "); - str.append("charge.name "); - str.append("when 'role_access_to_loan_products' then "); - str.append("lp.name "); - str.append("when 'role_access_to_savings_products' then "); - str.append("sp.name "); - str.append("end as to_name, "); - str.append("er.code_name "); - str.append("from m_entity_to_entity_mapping eem "); - str.append("join m_entity_relation er on eem.rel_id = er.id "); - str.append("left join m_office o on er.from_entity_type = 1 and eem.from_id = o.id "); - str.append("left join m_role r on er.from_entity_type = 5 and eem.from_id = r.id "); - str.append("left join m_product_loan lp on er.to_entity_type = 2 and eem.to_id = lp.id "); - str.append("left join m_savings_product sp on er.to_entity_type = 3 and eem.to_id = sp.id "); - str.append("left join m_charge charge on er.to_entity_type = 4 and eem.to_id = charge.id "); - str.append("where "); - str.append("er.id = ? and "); - str.append("( ? = 0 or from_id = ? ) and "); - str.append("( ? = 0 or to_id = ? ) "); - - this.schema = str.toString(); - } + private static final String ENTITY_TO_ENTITY_SCHEMA = """ + select eem.id as mapId, + eem.rel_id as relId, + eem.from_id as from_id, + eem.to_id as to_id, + eem.start_date as startDate, + eem.end_date as endDate, + case er.code_name + when 'office_access_to_loan_products' then + o.name + when 'office_access_to_savings_products' then + o.name + when 'office_access_to_fees/charges' then + o.name + when 'role_access_to_loan_products' then + r.name + when 'role_access_to_savings_products' then + r.name + end as from_name, + case er.code_name + when 'office_access_to_loan_products' then + lp.name + when 'office_access_to_savings_products' then + sp.name + when 'office_access_to_fees/charges' then + charge.name + when 'role_access_to_loan_products' then + lp.name + when 'role_access_to_savings_products' then + sp.name + end as to_name, + er.code_name + from m_entity_to_entity_mapping eem + join m_entity_relation er on eem.rel_id = er.id + left join m_office o on er.from_entity_type = 1 and eem.from_id = o.id + left join m_role r on er.from_entity_type = 5 and eem.from_id = r.id + left join m_product_loan lp on er.to_entity_type = 2 and eem.to_id = lp.id + left join m_savings_product sp on er.to_entity_type = 3 and eem.to_id = sp.id + left join m_charge charge on er.to_entity_type = 4 and eem.to_id = charge.id + where + er.id = ? and + ( ? = 0 or from_id = ? ) and + ( ? = 0 or to_id = ? )\s"""; + + EntityToEntityMapper() {} public String schema() { - return this.schema; + return ENTITY_TO_ENTITY_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/savings/transaction/SavingsAccountForceWithdrawalBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/savings/transaction/SavingsAccountForceWithdrawalBusinessEvent.java new file mode 100644 index 00000000000..f7d2e5c9c9e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/savings/transaction/SavingsAccountForceWithdrawalBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.event.business.domain.savings.transaction; + +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; + +public class SavingsAccountForceWithdrawalBusinessEvent extends SavingsAccountTransactionBusinessEvent { + + private static final String TYPE = "SavingsAccountForceWithdrawalBusinessEvent"; + + public SavingsAccountForceWithdrawalBusinessEvent(SavingsAccountTransaction value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java index 049a4889909..2406a34dbc8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanChargeDataMapper.java @@ -30,6 +30,7 @@ public interface LoanChargeDataMapper { @Mapping(target = "externalOwnerId", ignore = true) @Mapping(target = "customData", ignore = true) + @Mapping(target = "originators", ignore = true) LoanChargeDataV1 map(LoanChargeData source); LoanChargeDataRangeViewV1 mapRangeView(LoanChargeData source); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java index 12f092da7af..1ab32cd328a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/SchedulerJobApiResource.java @@ -90,8 +90,8 @@ public class SchedulerJobApiResource { private final SqlValidator sqlValidator; @GET - @Operation(summary = "Retrieve Scheduler Jobs", description = "Returns the list of jobs.\n" + "\n" + "Example Requests:\n" + "\n" - + "jobs") + @Operation(summary = "Retrieve Scheduler Jobs", operationId = "retrieveAllSchedulerJobs", description = "Returns the list of jobs.\n" + + "\n" + "Example Requests:\n" + "\n" + "jobs") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.GetJobsResponse.class)))) public String retrieveAll(@Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(SCHEDULER_RESOURCE_NAME); @@ -102,7 +102,8 @@ public String retrieveAll(@Context final UriInfo uriInfo) { @GET @Path("{" + SchedulerJobApiConstants.JOB_ID + "}") - @Operation(summary = "Retrieve a Job", description = "Returns the details of a Job.\n" + "\n" + "Example Requests:\n" + "\n" + "jobs/5") + @Operation(summary = "Retrieve a Job", operationId = "retrieveOneSchedulerJob", description = "Returns the details of a Job.\n" + "\n" + + "Example Requests:\n" + "\n" + "jobs/5") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SchedulerJobApiResourceSwagger.GetJobsResponse.class))) public String retrieveOne(@PathParam(SchedulerJobApiConstants.JOB_ID) @Parameter(description = "jobId") final Long jobId, @Context final UriInfo uriInfo) { @@ -227,7 +228,11 @@ private Response executeJob(@NotNull IdTypeResolver.IdType idType, String identi response = Response.status(400).build(); if (is(commandParam, SchedulerJobApiConstants.COMMAND_EXECUTE_JOB)) { Long jobId = schedulerJobRunnerReadService.retrieveId(idType, identifier); - jobRegisterService.executeJobWithParameters(jobId, jsonRequestBody); + final CommandWrapper commandRequest = new CommandWrapperBuilder() // + .executeSchedulerJob(jobId) // + .withJson(jsonRequestBody) // + .build(); + commandsSourceWritePlatformService.logCommandSource(commandRequest); response = Response.status(202).build(); } else { throw new UnrecognizedQueryParamException(SchedulerJobApiConstants.COMMAND, commandParam); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBApiFilter.java new file mode 100644 index 00000000000..567aab8f883 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBApiFilter.java @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.jobs.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; +import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; +import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException; +import org.apache.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public abstract class COBApiFilter extends OncePerRequestFilter { + + protected final COBFilterHelper helper; + + protected static class Reject { + + private final String message; + private final Integer statusCode; + + Reject(String message, Integer statusCode) { + this.message = message; + this.statusCode = statusCode; + } + + public static Reject reject(Long loanId, int status) { + return new Reject(ApiGlobalErrorResponse.loanIsLocked(loanId).toJson(), status); + } + + public void toServletResponse(HttpServletResponse response) throws IOException { + response.setStatus(statusCode); + response.getWriter().write(message); + } + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + request = new BodyCachingHttpServletRequestWrapper(request); + + if (!helper.isOnApiList((BodyCachingHttpServletRequestWrapper) request)) { + proceed(filterChain, request, response); + } else { + try { + boolean bypassUser = helper.isBypassUser(); + if (bypassUser) { + proceed(filterChain, request, response); + } else { + try { + List loanIds = helper.calculateRelevantLoanIds((BodyCachingHttpServletRequestWrapper) request); + if (!loanIds.isEmpty() && helper.isLoanBehind(loanIds)) { + helper.executeInlineCob(loanIds); + } + proceed(filterChain, request, response); + } catch (LoanIdsHardLockedException e) { + Reject.reject(e.getLoanIdFromRequest(), HttpStatus.SC_CONFLICT).toServletResponse(response); + } + } + } catch (UnAuthenticatedUserException e) { + throw new AuthenticationCredentialsNotFoundException("Not Authenticated", e); + } + } + } + + private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + filterChain.doFilter(request, response); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterApiMatcher.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterApiMatcher.java new file mode 100644 index 00000000000..d837252626d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterApiMatcher.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.jobs.filter; + +import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; + +public abstract class COBFilterApiMatcher implements COBFilterHelper { + + protected final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public boolean isOnApiList(BodyCachingHttpServletRequestWrapper request) throws IOException { + String pathInfo = request.getPathInfo(); + String method = request.getMethod(); + if (StringUtils.isBlank(pathInfo)) { + return false; + } + if (isBatchApi(pathInfo)) { + return isBatchApiMatching(request); + } else { + return isApiMatching(method, pathInfo); + } + } + + protected boolean isBatchApiMatching(BodyCachingHttpServletRequestWrapper request) throws IOException { + for (BatchRequest batchRequest : getBatchRequests(request)) { + String method = batchRequest.getMethod(); + String pathInfo = batchRequest.getRelativeUrl(); + if (isApiMatching(method, pathInfo)) { + return true; + } + } + return false; + } + + protected boolean isBatchApi(String pathInfo) { + return pathInfo.startsWith("/v1/batches"); + } + + protected List getBatchRequests(BodyCachingHttpServletRequestWrapper request) throws IOException { + List batchRequests = objectMapper.readValue(request.getInputStream(), new TypeReference<>() {}); + // since we read body, we have to reset so the upcoming readings are successful + request.resetStream(); + for (BatchRequest batchRequest : batchRequests) { + String pathInfo = "/" + batchRequest.getRelativeUrl(); + if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) { + pathInfo = "/v1/" + batchRequest.getRelativeUrl(); + } + batchRequest.setRelativeUrl(pathInfo); + } + return batchRequests; + } + + protected abstract boolean isApiMatching(String method, String pathInfo); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterHelper.java new file mode 100644 index 00000000000..1df77663fed --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/COBFilterHelper.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.jobs.filter; + +import java.io.IOException; +import java.util.List; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; + +public interface COBFilterHelper { + + boolean isOnApiList(BodyCachingHttpServletRequestWrapper request) throws IOException; + + boolean isBypassUser(); + + List calculateRelevantLoanIds(BodyCachingHttpServletRequestWrapper request) throws IOException; + + boolean isLoanBehind(List loanIds); + + void executeInlineCob(List loanIds); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java index 65073621afe..82a3240bb5d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java @@ -18,81 +18,14 @@ */ package org.apache.fineract.infrastructure.jobs.filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; -import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; -import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; -import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; -import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException; -import org.apache.http.HttpStatus; import org.springframework.context.annotation.Conditional; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.web.filter.OncePerRequestFilter; -@RequiredArgsConstructor @Conditional(LoanCOBEnabledCondition.class) -public class LoanCOBApiFilter extends OncePerRequestFilter { +public class LoanCOBApiFilter extends COBApiFilter { - private final LoanCOBFilterHelper helper; - - private static class Reject { - - private final String message; - private final Integer statusCode; - - Reject(String message, Integer statusCode) { - this.message = message; - this.statusCode = statusCode; - } - - public static Reject reject(Long loanId, int status) { - return new Reject(ApiGlobalErrorResponse.loanIsLocked(loanId).toJson(), status); - } - - public void toServletResponse(HttpServletResponse response) throws IOException { - response.setStatus(statusCode); - response.getWriter().write(message); - } - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - request = new BodyCachingHttpServletRequestWrapper(request); - - if (!helper.isOnApiList((BodyCachingHttpServletRequestWrapper) request)) { - proceed(filterChain, request, response); - } else { - try { - boolean bypassUser = helper.isBypassUser(); - if (bypassUser) { - proceed(filterChain, request, response); - } else { - try { - List loanIds = helper.calculateRelevantLoanIds((BodyCachingHttpServletRequestWrapper) request); - if (!loanIds.isEmpty() && helper.isLoanBehind(loanIds)) { - helper.executeInlineCob(loanIds); - } - proceed(filterChain, request, response); - } catch (LoanIdsHardLockedException e) { - Reject.reject(e.getLoanIdFromRequest(), HttpStatus.SC_CONFLICT).toServletResponse(response); - } - } - } catch (UnAuthenticatedUserException e) { - throw new AuthenticationCredentialsNotFoundException("Not Authenticated", e); - } - } - } - - private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - filterChain.doFilter(request, response); + public LoanCOBApiFilter(LoanCOBFilterHelper helper) { + super(helper); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java index d1d79484216..4d0b7248755 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java @@ -18,266 +18,4 @@ */ package org.apache.fineract.infrastructure.jobs.filter; -import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.json.JsonReadFeature; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Lists; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import lombok.RequiredArgsConstructor; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.fineract.batch.domain.BatchRequest; -import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; -import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; -import org.apache.fineract.cob.loan.LoanCOBConstant; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; -import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; -import org.apache.fineract.cob.service.LoanAccountLockService; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.domain.ExternalId; -import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; -import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.annotation.Conditional; -import org.springframework.http.HttpMethod; -import org.springframework.stereotype.Component; - -@RequiredArgsConstructor -@Component -@Conditional(LoanCOBEnabledCondition.class) -public class LoanCOBFilterHelper implements InitializingBean { - - private final GLIMAccountInfoRepository glimAccountInfoRepository; - private final LoanAccountLockService loanAccountLockService; - private final PlatformSecurityContext context; - private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService; - private final LoanRepository loanRepository; - private final FineractProperties fineractProperties; - private final RetrieveLoanIdService retrieveLoanIdService; - - private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; - private final ObjectMapper objectMapper = new ObjectMapper(); - - private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); - - public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/catch-up"); - public static final Pattern LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/(?:reschedule)?loans/(?:external-id/)?([^/?]+).*"); - - public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*"); - private static final Predicate URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find() - || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find(); - - private Long getLoanId(boolean isGlim, String pathInfo) { - if (!isGlim) { - String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"); - if (isExternal(pathInfo)) { - String externalId = id; - return loanRepository.findIdByExternalId(new ExternalId(externalId)); - } else if (isRescheduleLoans(pathInfo)) { - return loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null); - } else if (StringUtils.isNumeric(id)) { - return Long.valueOf(id); - } else { - return null; - } - } else { - return Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1")); - } - } - - private boolean isExternal(String pathInfo) { - return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("external-id"); - } - - private boolean isRescheduleLoans(String pathInfo) { - return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/"); - } - - public boolean isOnApiList(BodyCachingHttpServletRequestWrapper request) throws IOException { - String pathInfo = request.getPathInfo(); - String method = request.getMethod(); - if (StringUtils.isBlank(pathInfo)) { - return false; - } - if (isBatchApi(pathInfo)) { - return isBatchApiMatching(request); - } else { - return isApiMatching(method, pathInfo); - } - } - - private boolean isBatchApiMatching(BodyCachingHttpServletRequestWrapper request) throws IOException { - for (BatchRequest batchRequest : getBatchRequests(request)) { - String method = batchRequest.getMethod(); - String pathInfo = batchRequest.getRelativeUrl(); - if (isApiMatching(method, pathInfo)) { - return true; - } - } - return false; - } - - private List getBatchRequests(BodyCachingHttpServletRequestWrapper request) throws IOException { - List batchRequests = objectMapper.readValue(request.getInputStream(), new TypeReference<>() {}); - // since we read body, we have to reset so the upcoming readings are successful - request.resetStream(); - for (BatchRequest batchRequest : batchRequests) { - String pathInfo = "/" + batchRequest.getRelativeUrl(); - if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) { - pathInfo = "/v1/" + batchRequest.getRelativeUrl(); - } - batchRequest.setRelativeUrl(pathInfo); - } - return batchRequests; - } - - private boolean isApiMatching(String method, String pathInfo) { - return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() - && URL_FUNCTION.test(pathInfo); - } - - private boolean isBatchApi(String pathInfo) { - return pathInfo.startsWith("/v1/batches"); - } - - private boolean isGlim(String pathInfo) { - return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches(); - } - - public boolean isBypassUser() { - return context.authenticatedUser().isBypassUser(); - } - - private List getGlimChildLoanIds(Long loanIdFromRequest) { - GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true, - BigDecimal.valueOf(loanIdFromRequest)); - if (glimAccount != null) { - return glimAccount.getChildLoan().stream().map(Loan::getId).toList(); - } else { - return Collections.emptyList(); - } - } - - private boolean isLoanHardLocked(Long... loanIds) { - return isLoanHardLocked(Arrays.asList(loanIds)); - } - - private boolean isLoanHardLocked(List loanIds) { - return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked); - } - - private boolean isLockOverrulable(Long... loanIds) { - return isLockOverrulable(Arrays.asList(loanIds)); - } - - private boolean isLockOverrulable(List loanIds) { - return loanIds.stream().anyMatch(loanAccountLockService::isLockOverrulable); - } - - public boolean isLoanBehind(List loanIds) { - List loanIdAndLastClosedBusinessDates = new ArrayList<>(); - List> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); - partitions.forEach(partition -> { - loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService - .retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); - loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService.retrieveLoanBehindOnDisbursementDate( - ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); - }); - return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates); - } - - public List calculateRelevantLoanIds(BodyCachingHttpServletRequestWrapper request) throws IOException { - String pathInfo = request.getPathInfo(); - if (isBatchApi(pathInfo)) { - return getLoanIdsFromBatchApi(request); - } else { - return getLoanIdsFromApi(pathInfo); - } - } - - private List getLoanIdsFromBatchApi(BodyCachingHttpServletRequestWrapper request) throws IOException { - List loanIds = new ArrayList<>(); - for (BatchRequest batchRequest : getBatchRequests(request)) { - // check the URL for Loan related ID - String relativeUrl = batchRequest.getRelativeUrl(); - if (!relativeUrl.contains("$.resourceId")) { - // if resourceId reference is used, we simply don't know the resourceId without executing the requests - // first, so skipping it - loanIds.addAll(getLoanIdsFromApi(relativeUrl)); - } - - // check the body for Loan ID - Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest); - if (loanId != null) { - if (isLoanHardLocked(loanId) && !isLockOverrulable(loanId)) { - throw new LoanIdsHardLockedException(loanId); - } else { - loanIds.add(loanId); - } - } - } - return loanIds; - } - - private Long getTopLevelLoanIdFromBatchRequest(BatchRequest batchRequest) throws JsonProcessingException { - String body = batchRequest.getBody(); - if (StringUtils.isNotBlank(body)) { - JsonNode jsonNode = objectMapper.readTree(body); - if (jsonNode.has("loanId")) { - return jsonNode.get("loanId").asLong(); - } - } - return null; - } - - private List getLoanIdsFromApi(String pathInfo) { - List loanIds = getLoanIdList(pathInfo); - if (isLoanHardLocked(loanIds) && !isLockOverrulable(loanIds)) { - throw new LoanIdsHardLockedException(loanIds.get(0)); - } else { - return loanIds; - } - } - - private List getLoanIdList(String pathInfo) { - boolean isGlim = isGlim(pathInfo); - Long loanIdFromRequest = getLoanId(isGlim, pathInfo); - if (loanIdFromRequest == null) { - return Collections.emptyList(); - } - if (isGlim) { - return getGlimChildLoanIds(loanIdFromRequest); - } else { - return Collections.singletonList(loanIdFromRequest); - } - } - - public void executeInlineCob(List loanIds) { - inlineLoanCOBExecutorService.execute(loanIds, LoanCOBConstant.INLINE_LOAN_COB_JOB_NAME); - } - - @Override - public void afterPropertiesSet() throws Exception { - objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); - } - -} +public interface LoanCOBFilterHelper extends COBFilterHelper {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperImpl.java new file mode 100644 index 00000000000..c6dfc2998c1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperImpl.java @@ -0,0 +1,242 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.jobs.filter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.cob.loan.LoanCOBConstant; +import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; +import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.cob.service.RetrieveLoanIdService; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; +import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +@Conditional(LoanCOBEnabledCondition.class) +public class LoanCOBFilterHelperImpl extends COBFilterApiMatcher implements LoanCOBFilterHelper, InitializingBean { + + private final GLIMAccountInfoRepository glimAccountInfoRepository; + private final LoanAccountLockService loanAccountLockService; + private final PlatformSecurityContext context; + private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService; + private final LoanRepository loanRepository; + private final FineractProperties fineractProperties; + private final RetrieveLoanIdService retrieveIdService; + + private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; + + private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); + + public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/catch-up"); + public static final Pattern LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/(?:reschedule)?loans/(?:external-id/)?([^/?]+).*"); + + public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*"); + private static final Predicate URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find() + || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find(); + + private Long getLoanId(boolean isGlim, String pathInfo) { + if (!isGlim) { + String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"); + if (isExternal(pathInfo)) { + String externalId = id; + return loanRepository.findIdByExternalId(new ExternalId(externalId)); + } else if (isRescheduleLoans(pathInfo)) { + return loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null); + } else if (StringUtils.isNumeric(id)) { + return Long.valueOf(id); + } else { + return null; + } + } else { + return Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1")); + } + } + + private boolean isExternal(String pathInfo) { + return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("external-id"); + } + + private boolean isRescheduleLoans(String pathInfo) { + return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/"); + } + + @Override + protected boolean isApiMatching(String method, String pathInfo) { + return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() + && URL_FUNCTION.test(pathInfo); + } + + private boolean isGlim(String pathInfo) { + return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches(); + } + + @Override + public boolean isBypassUser() { + return context.authenticatedUser().isBypassUser(); + } + + private List getGlimChildLoanIds(Long loanIdFromRequest) { + GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true, + BigDecimal.valueOf(loanIdFromRequest)); + if (glimAccount != null) { + return glimAccount.getChildLoan().stream().map(Loan::getId).toList(); + } else { + return Collections.emptyList(); + } + } + + private boolean isLoanHardLocked(Long... loanIds) { + return isLoanHardLocked(Arrays.asList(loanIds)); + } + + private boolean isLoanHardLocked(List loanIds) { + return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked); + } + + private boolean isLockOverrulable(Long... loanIds) { + return isLockOverrulable(Arrays.asList(loanIds)); + } + + private boolean isLockOverrulable(List loanIds) { + return loanIds.stream().anyMatch(loanAccountLockService::isLockOverrulable); + } + + @Override + public boolean isLoanBehind(List loanIds) { + List loanIdAndLastClosedBusinessDates = new ArrayList<>(); + List> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); + partitions.forEach(partition -> { + loanIdAndLastClosedBusinessDates.addAll(retrieveIdService + .retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); + loanIdAndLastClosedBusinessDates.addAll(retrieveIdService.retrieveLoanBehindOnDisbursementDate( + ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); + }); + return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates); + } + + @Override + public List calculateRelevantLoanIds(BodyCachingHttpServletRequestWrapper request) throws IOException { + String pathInfo = request.getPathInfo(); + if (isBatchApi(pathInfo)) { + return getLoanIdsFromBatchApi(request); + } else { + return getLoanIdsFromApi(pathInfo); + } + } + + private List getLoanIdsFromBatchApi(BodyCachingHttpServletRequestWrapper request) throws IOException { + List loanIds = new ArrayList<>(); + for (BatchRequest batchRequest : getBatchRequests(request)) { + // check the URL for Loan related ID + String relativeUrl = batchRequest.getRelativeUrl(); + if (!relativeUrl.contains("$.resourceId")) { + // if resourceId reference is used, we simply don't know the resourceId without executing the requests + // first, so skipping it + loanIds.addAll(getLoanIdsFromApi(relativeUrl)); + } + + // check the body for Loan ID + Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest); + if (loanId != null) { + if (isLoanHardLocked(loanId) && !isLockOverrulable(loanId)) { + throw new LoanIdsHardLockedException(loanId); + } else { + loanIds.add(loanId); + } + } + } + return loanIds; + } + + private Long getTopLevelLoanIdFromBatchRequest(BatchRequest batchRequest) throws JsonProcessingException { + String body = batchRequest.getBody(); + if (StringUtils.isNotBlank(body)) { + JsonNode jsonNode = objectMapper.readTree(body); + if (jsonNode.has("loanId")) { + return jsonNode.get("loanId").asLong(); + } + } + return null; + } + + private List getLoanIdsFromApi(String pathInfo) { + List loanIds = getLoanIdList(pathInfo); + if (isLoanHardLocked(loanIds) && !isLockOverrulable(loanIds)) { + throw new LoanIdsHardLockedException(loanIds.get(0)); + } else { + return loanIds; + } + } + + private List getLoanIdList(String pathInfo) { + boolean isGlim = isGlim(pathInfo); + Long loanIdFromRequest = getLoanId(isGlim, pathInfo); + if (loanIdFromRequest == null) { + return Collections.emptyList(); + } + if (isGlim) { + return getGlimChildLoanIds(loanIdFromRequest); + } else { + return Collections.singletonList(loanIdFromRequest); + } + } + + @Override + public void executeInlineCob(List loanIds) { + inlineLoanCOBExecutorService.execute(loanIds, LoanCOBConstant.INLINE_LOAN_COB_JOB_NAME); + } + + @Override + public void afterPropertiesSet() throws Exception { + objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/ProgressiveLoanModelCheckerHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/ProgressiveLoanModelCheckerHelper.java index c6793fb6da6..221cb7080a5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/ProgressiveLoanModelCheckerHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/ProgressiveLoanModelCheckerHelper.java @@ -18,11 +18,8 @@ */ package org.apache.fineract.infrastructure.jobs.filter; -import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.json.JsonReadFeature; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; @@ -33,6 +30,7 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -48,7 +46,7 @@ @RequiredArgsConstructor @Component -public class ProgressiveLoanModelCheckerHelper implements InitializingBean { +public class ProgressiveLoanModelCheckerHelper extends COBFilterApiMatcher implements InitializingBean { private final GLIMAccountInfoRepository glimAccountInfoRepository; private final LoanRepository loanRepository; @@ -91,53 +89,12 @@ private boolean isRescheduleLoans(String pathInfo) { return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/"); } - public boolean isOnApiList(BodyCachingHttpServletRequestWrapper request) throws IOException { - String pathInfo = request.getPathInfo(); - String method = request.getMethod(); - if (StringUtils.isBlank(pathInfo)) { - return false; - } - if (isBatchApi(pathInfo)) { - return isBatchApiMatching(request); - } else { - return isApiMatching(method, pathInfo); - } - } - - private boolean isBatchApiMatching(BodyCachingHttpServletRequestWrapper request) throws IOException { - for (BatchRequest batchRequest : getBatchRequests(request)) { - String method = batchRequest.getMethod(); - String pathInfo = batchRequest.getRelativeUrl(); - if (isApiMatching(method, pathInfo)) { - return true; - } - } - return false; - } - - private List getBatchRequests(BodyCachingHttpServletRequestWrapper request) throws IOException { - List batchRequests = objectMapper.readValue(request.getInputStream(), new TypeReference<>() {}); - // since we read body, we have to reset so the upcoming readings are successful - request.resetStream(); - for (BatchRequest batchRequest : batchRequests) { - String pathInfo = "/" + batchRequest.getRelativeUrl(); - if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) { - pathInfo = "/v1/" + batchRequest.getRelativeUrl(); - } - batchRequest.setRelativeUrl(pathInfo); - } - return batchRequests; - } - - private boolean isApiMatching(String method, String pathInfo) { + @Override + protected boolean isApiMatching(String method, String pathInfo) { return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() && URL_FUNCTION.test(pathInfo); } - private boolean isBatchApi(String pathInfo) { - return pathInfo.startsWith("/v1/batches"); - } - private boolean isGlim(String pathInfo) { return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches(); } @@ -152,6 +109,7 @@ private List getGlimChildLoanIds(Long loanIdFromRequest) { } } + @Override public List calculateRelevantLoanIds(BodyCachingHttpServletRequestWrapper request) throws IOException { String pathInfo = request.getPathInfo(); if (isBatchApi(pathInfo)) { @@ -214,4 +172,19 @@ public void afterPropertiesSet() throws Exception { objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); } + @Override + public boolean isBypassUser() { + throw new NotImplementedException(); + } + + @Override + public boolean isLoanBehind(List loanIds) { + throw new NotImplementedException(); + } + + @Override + public void executeInlineCob(List loanIds) { + throw new NotImplementedException(); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBApiFilter.java new file mode 100644 index 00000000000..d30c970ee10 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBApiFilter.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.jobs.filter; + +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.springframework.context.annotation.Conditional; + +@Conditional(LoanCOBEnabledCondition.class) +public class WorkingCapitalLoanCOBApiFilter extends COBApiFilter { + + public WorkingCapitalLoanCOBApiFilter(WorkingCapitalLoanCOBFilterHelper helper) { + super(helper); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelper.java new file mode 100644 index 00000000000..f03f8aebdf9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelper.java @@ -0,0 +1,22 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.jobs.filter; + +public interface WorkingCapitalLoanCOBFilterHelper extends COBFilterHelper {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelperImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelperImpl.java new file mode 100644 index 00000000000..cd9e39f55e0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/WorkingCapitalLoanCOBFilterHelperImpl.java @@ -0,0 +1,209 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.infrastructure.jobs.filter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.service.AbstractAccountLockService; +import org.apache.fineract.cob.service.InlineCommonLockableCOBExecutorService; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant; +import org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanRetrieveIdService; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +@Conditional(LoanCOBEnabledCondition.class) +public class WorkingCapitalLoanCOBFilterHelperImpl extends COBFilterApiMatcher + implements WorkingCapitalLoanCOBFilterHelper, InitializingBean { + + private final AbstractAccountLockService loanAccountLockService; + private final PlatformSecurityContext context; + private final InlineCommonLockableCOBExecutorService inlineLoanCOBExecutorService; + private final WorkingCapitalLoanRepository loanRepository; + private final FineractProperties fineractProperties; + private final WorkingCapitalLoanRetrieveIdService retrieveIdService; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private static final List HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); + + public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/working-capital-loans/catch-up"); + public static final Pattern LOAN_PATH_PATTERN = Pattern + .compile("/v[1-9][0-9]*/(?:reschedule)?working-capital-loans/(?:external-id/)?([^/?]+).*"); + private static final Predicate URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find(); + + private Long getLoanId(String pathInfo) { + String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"); + if (isExternal(pathInfo)) { + String externalId = id; + return loanRepository.findIdByExternalId(new ExternalId(externalId)); + } else if (StringUtils.isNumeric(id)) { + return Long.valueOf(id); + } else { + return null; + } + + } + + private boolean isExternal(String pathInfo) { + return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("external-id"); + } + + @Override + protected boolean isApiMatching(String method, String pathInfo) { + return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find() + && URL_FUNCTION.test(pathInfo); + } + + @Override + public boolean isBypassUser() { + return context.authenticatedUser().isBypassUser(); + } + + private boolean isLoanHardLocked(Long... loanIds) { + return isLoanHardLocked(Arrays.asList(loanIds)); + } + + private boolean isLoanHardLocked(List loanIds) { + return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked); + } + + private boolean isLockOverrulable(Long... loanIds) { + return isLockOverrulable(Arrays.asList(loanIds)); + } + + private boolean isLockOverrulable(List loanIds) { + return loanIds.stream().anyMatch(loanAccountLockService::isLockOverrulable); + } + + @Override + public boolean isLoanBehind(List loanIds) { + List loanIdAndLastClosedBusinessDates = new ArrayList<>(); + List> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit()); + partitions.forEach(partition -> { + loanIdAndLastClosedBusinessDates.addAll(retrieveIdService + .retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); + loanIdAndLastClosedBusinessDates.addAll(retrieveIdService.retrieveLoanBehindOnDisbursementDate( + ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)); + }); + return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates); + } + + @Override + public List calculateRelevantLoanIds(BodyCachingHttpServletRequestWrapper request) throws IOException { + String pathInfo = request.getPathInfo(); + if (isBatchApi(pathInfo)) { + return getLoanIdsFromBatchApi(request); + } else { + return getLoanIdsFromApi(pathInfo); + } + } + + private List getLoanIdsFromBatchApi(BodyCachingHttpServletRequestWrapper request) throws IOException { + List loanIds = new ArrayList<>(); + for (BatchRequest batchRequest : getBatchRequests(request)) { + // check the URL for Loan related ID + String relativeUrl = batchRequest.getRelativeUrl(); + if (!relativeUrl.contains("$.resourceId")) { + // if resourceId reference is used, we simply don't know the resourceId without executing the requests + // first, so skipping it + loanIds.addAll(getLoanIdsFromApi(relativeUrl)); + } + + // check the body for Loan ID + Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest); + if (loanId != null) { + if (isLoanHardLocked(loanId) && !isLockOverrulable(loanId)) { + throw new LoanIdsHardLockedException(loanId); + } else { + loanIds.add(loanId); + } + } + } + return loanIds; + } + + private Long getTopLevelLoanIdFromBatchRequest(BatchRequest batchRequest) throws JsonProcessingException { + String body = batchRequest.getBody(); + if (StringUtils.isNotBlank(body)) { + JsonNode jsonNode = objectMapper.readTree(body); + if (jsonNode.has("loanId")) { + return jsonNode.get("loanId").asLong(); + } + } + return null; + } + + private List getLoanIdsFromApi(String pathInfo) { + List loanIds = getLoanIdList(pathInfo); + if (isLoanHardLocked(loanIds) && !isLockOverrulable(loanIds)) { + throw new LoanIdsHardLockedException(loanIds.getFirst()); + } else { + return loanIds; + } + } + + private List getLoanIdList(String pathInfo) { + Long loanIdFromRequest = getLoanId(pathInfo); + if (loanIdFromRequest == null) { + return Collections.emptyList(); + } + return Collections.singletonList(loanIdFromRequest); + } + + @Override + public void executeInlineCob(List loanIds) { + inlineLoanCOBExecutorService.execute(loanIds, WorkingCapitalLoanCOBConstant.INLINE_WORKING_CAPITAL_LOAN_COB_JOB_NAME); + } + + @Override + public void afterPropertiesSet() throws Exception { + objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java new file mode 100644 index 00000000000..83afa584db2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.jobs.handler; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@CommandType(entity = "SCHEDULER", action = "EXECUTEJOB") +public class ExecuteJobCommandHandler implements NewCommandSourceHandler { + + private final JobRegisterService jobRegisterService; + + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + final Long jobId = command.entityId(); + jobRegisterService.executeJobWithParameters(jobId, command.json()); + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(jobId) // + .build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java index e8b2ce18c22..3b1ed4217f3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java @@ -23,11 +23,13 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; +import org.apache.fineract.cob.service.InlineWorkingCapitalLoanCOBExecutorServiceImpl; @RequiredArgsConstructor public enum InlineJobType { - LOAN_COB("LOAN_COB", "INLINE_LOAN_COB", InlineLoanCOBExecutorServiceImpl.class); + LOAN_COB("LOAN_COB", "INLINE_LOAN_COB", InlineLoanCOBExecutorServiceImpl.class), WC_LOAN_COB("WC_LOAN_COB", + "INLINE_WORKING_CAPITAL_LOAN_COB", InlineWorkingCapitalLoanCOBExecutorServiceImpl.class); private final String jobName; @Getter diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java index a2ee619b06a..ec1013d04f3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/AbstractJobParameterProvider.java @@ -18,12 +18,14 @@ */ package org.apache.fineract.infrastructure.jobs.service.jobparameterprovider; +import java.util.List; + public abstract class AbstractJobParameterProvider implements JobParameterProvider { @Override public boolean canProvideParametersForJob(String jobName) { - return jobName.equals(getJobName()); + return getJobNames().contains(jobName); } - protected abstract String getJobName(); + protected abstract List getJobNames(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java index 557aaeb234a..554f4a8837a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/jobparameterprovider/LoanCOBJobParameterProvider.java @@ -21,6 +21,7 @@ import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -52,8 +53,8 @@ public Map> provide(Set jobParameter } @Override - public String getJobName() { - return JobName.LOAN_COB.name(); + protected List getJobNames() { + return List.of(JobName.LOAN_COB.name(), JobName.WORKING_CAPITAL_LOAN_COB_JOB.name()); } private Set getJobParameterDTOListWithCorrectBusinessDate(Set jobParameterDTOset) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsChecker.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsChecker.java new file mode 100644 index 00000000000..f840da55f74 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/PlatformUserDetailsChecker.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.service; + +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.stereotype.Component; + +/** + * Checks user details during Spring Security authentication. Password reset enforcement is handled by + * SpringSecurityPlatformSecurityContext and AuthenticationApiResource after authentication succeeds. + */ +@Component +public class PlatformUserDetailsChecker implements UserDetailsChecker { + + @Override + public void check(UserDetails userDetails) { + if (!userDetails.isCredentialsNonExpired()) { + throw new CredentialsExpiredException("User credentials have expired"); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java index eb1c4a81690..57ad8ba6c9d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/service/SmsReadPlatformServiceImpl.java @@ -62,27 +62,23 @@ public void init() { private static final class SmsMapper implements RowMapper { - final String schema; - - SmsMapper() { - final StringBuilder sql = new StringBuilder(300); - sql.append("smo.id as id, "); - sql.append("smo.group_id as groupId, "); - sql.append("smo.client_id as clientId, "); - sql.append("smo.staff_id as staffId, "); - sql.append("smo.status_enum as statusId, "); - sql.append("smo.mobile_no as mobileNo, "); - sql.append("smo.message as message, "); - sql.append("smc.provider_id as providerId, "); - sql.append("smc.campaign_name as campaignName "); - sql.append("from sms_messages_outbound smo "); - sql.append("join sms_campaign smc on smc.id = smo.campaign_id "); - - this.schema = sql.toString(); - } + private static final String SMS_SCHEMA = """ + smo.id as id, + smo.group_id as groupId, + smo.client_id as clientId, + smo.staff_id as staffId, + smo.status_enum as statusId, + smo.mobile_no as mobileNo, + smo.message as message, + smc.provider_id as providerId, + smc.campaign_name as campaignName + from sms_messages_outbound smo + join sms_campaign smc on smc.id = smo.campaign_id\s"""; + + SmsMapper() {} public String schema() { - return this.schema; + return SMS_SCHEMA; } public String tableName() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java index 23157ed7e1f..a8414585e63 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java @@ -44,6 +44,7 @@ import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.exception.ErrorHandler; @@ -134,6 +135,7 @@ public class InteropServiceImpl implements InteropService { private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; private final SavingsAccountDomainService savingsAccountService; + private final ConfigurationDomainService configurationDomainService; private final JdbcTemplate jdbcTemplate; @@ -566,7 +568,7 @@ private Loan validateAndGetLoan(String accountId) { private SavingsAccount validateAndGetSavingAccount(@NonNull InteropRequestData request) { // TODO: error handling SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId()); - savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper); + savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper, configurationDomainService); ApplicationCurrency requestCurrency = currencyRepository.findOneByCode(request.getAmount().getCurrency()); if (!savingsAccount.getCurrency().getCode().equals(requestCurrency.getCode())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java index 325baf58840..69ca7dfefdd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/starter/InteroperationConfiguration.java @@ -19,6 +19,7 @@ package org.apache.fineract.interoperation.starter; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -52,12 +53,12 @@ public InteropService interopService(PlatformSecurityContext securityContext, In PaymentTypeRepository paymentTypeRepository, InteropIdentifierRepository identifierRepository, LoanRepositoryWrapper loanRepositoryWrapper, SavingsHelper savingsHelper, SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, - SavingsAccountDomainService savingsAccountService, JdbcTemplate jdbcTemplate, - PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + SavingsAccountDomainService savingsAccountService, ConfigurationDomainService configurationDomainService, + JdbcTemplate jdbcTemplate, PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, DefaultToApiJsonSerializer toApiJsonSerializer, DatabaseSpecificSQLGenerator sqlGenerator) { return new InteropServiceImpl(securityContext, interopDataValidator, savingsAccountRepository, savingsAccountTransactionRepository, applicationCurrencyRepository, noteRepository, paymentTypeRepository, identifierRepository, loanRepositoryWrapper, - savingsHelper, savingsAccountTransactionSummaryWrapper, savingsAccountService, jdbcTemplate, + savingsHelper, savingsAccountTransactionSummaryWrapper, savingsAccountService, configurationDomainService, jdbcTemplate, commandsSourceWritePlatformService, toApiJsonSerializer, sqlGenerator); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java b/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java deleted file mode 100644 index 7510f0d42e6..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/data/ContextData.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.data; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Data -@NoArgsConstructor -@Accessors(chain = true) -public class ContextData { - - private String dimensionType; - private String dimension; - private Integer periodType; - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (this.dimension == null ? 0 : this.dimension.hashCode()); - result = prime * result + (this.dimensionType == null ? 0 : this.dimensionType.hashCode()); - result = prime * result + (this.periodType == null ? 0 : this.periodType.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof ContextData)) { - return false; - } - final ContextData other = (ContextData) obj; - if (this.dimension == null) { - if (other.dimension != null) { - return false; - } - } else if (!this.dimension.equals(other.dimension)) { - return false; - } - if (this.dimensionType == null) { - if (other.dimensionType != null) { - return false; - } - } else if (!this.dimensionType.equals(other.dimensionType)) { - return false; - } - if (this.periodType == null) { - if (other.periodType != null) { - return false; - } - } else if (!this.periodType.equals(other.periodType)) { - return false; - } - return true; - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java b/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java deleted file mode 100644 index dbeb1318350..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/domain/MixTaxonomyMapping.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.domain; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.experimental.Accessors; -import org.apache.commons.lang3.StringUtils; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; - -@Entity -@Table(name = "mix_taxonomy_mapping") -@Getter -@Setter -@NoArgsConstructor -@Accessors(chain = true) -public final class MixTaxonomyMapping extends AbstractPersistableCustom { - - @Column(name = "identifier") - private String identifier; - - @Column(name = "config") - private String config; - - @Column(name = "currency") - private String currency; - - private MixTaxonomyMapping(final String identifier, final String config, final String currency) { - this.identifier = StringUtils.defaultIfEmpty(identifier, null); - this.config = StringUtils.defaultIfEmpty(config, null); - this.currency = StringUtils.defaultIfEmpty(currency, null); - } - - public static MixTaxonomyMapping fromJson(final JsonCommand command) { - final String identifier = command.stringValueOfParameterNamed("identifier"); - final String config = command.stringValueOfParameterNamed("config"); - final String currency = command.stringValueOfParameterNamed("currency"); - return new MixTaxonomyMapping(identifier, config, currency); - } - - public void update(final JsonCommand command) { - - this.identifier = command.stringValueOfParameterNamed("identifier"); - this.config = command.stringValueOfParameterNamed("config"); - this.currency = command.stringValueOfParameterNamed("currency"); - - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java deleted file mode 100644 index 367ef80c864..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingReadPlatformServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.service; - -import java.sql.ResultSet; -import java.sql.SQLException; -import lombok.RequiredArgsConstructor; -import org.apache.fineract.mix.data.MixTaxonomyMappingData; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - -@RequiredArgsConstructor -public class MixTaxonomyMappingReadPlatformServiceImpl implements MixTaxonomyMappingReadPlatformService { - - private final JdbcTemplate jdbcTemplate; - - private static final class TaxonomyMappingMapper implements RowMapper { - - public String schema() { - return "identifier, config " + "from mix_taxonomy_mapping"; - } - - @Override - public MixTaxonomyMappingData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final String identifier = rs.getString("identifier"); - final String config = rs.getString("config"); - return new MixTaxonomyMappingData().setIdentifier(identifier).setConfig(config); - } - - } - - @Override - public MixTaxonomyMappingData retrieveTaxonomyMapping() { - try { - final TaxonomyMappingMapper rm = new TaxonomyMappingMapper(); - final String sqlString = "select " + rm.schema(); - return this.jdbcTemplate.queryForObject(sqlString, rm); // NOSONAR - } catch (final EmptyResultDataAccessException e) { - return null; - } - - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java deleted file mode 100644 index bb6a5424ad4..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyMappingWritePlatformServiceImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.service; - -import lombok.RequiredArgsConstructor; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; -import org.apache.fineract.mix.domain.MixTaxonomyMapping; -import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -public class MixTaxonomyMappingWritePlatformServiceImpl implements MixTaxonomyMappingWritePlatformService { - - private final MixTaxonomyMappingRepository mappingRepository; - - @Transactional - @Override - public CommandProcessingResult updateMapping(final Long mappingId, final JsonCommand command) { - try { - MixTaxonomyMapping mapping = this.mappingRepository.findById(mappingId).orElse(null); - if (mapping == null) { - mapping = MixTaxonomyMapping.fromJson(command); - } else { - mapping.update(command); - } - - this.mappingRepository.saveAndFlush(mapping); - - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(mapping.getId()).build(); - - } catch (final JpaSystemException | DataIntegrityViolationException dve) { - return CommandProcessingResult.empty(); - } - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java deleted file mode 100644 index eb4eb0fb715..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/MixTaxonomyReadPlatformServiceImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.service; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; -import org.apache.fineract.mix.data.MixTaxonomyData; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - -public class MixTaxonomyReadPlatformServiceImpl implements MixTaxonomyReadPlatformService { - - private final JdbcTemplate jdbcTemplate; - private final MixTaxonomyMapper mixTaxonomyMapper; - - public MixTaxonomyReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - this.mixTaxonomyMapper = new MixTaxonomyMapper(); - } - - private static final class MixTaxonomyMapper implements RowMapper { - - public String schema() { - return "tx.id as id, name, dimension, type, description, prefix " - + "from mix_taxonomy tx left join mix_xbrl_namespace xn on tx.namespace_id=xn.id"; - } - - @Override - public MixTaxonomyData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final long id = rs.getLong("id"); - final String name = rs.getString("name"); - final String namespace = rs.getString("prefix"); - - final String dimension = rs.getString("dimension"); - final Integer type = rs.getInt("type"); - final String desc = rs.getString("description"); - return new MixTaxonomyData().setId(id).setName(name).setNamespace(namespace).setDimension(dimension).setType(type) - .setDescription(desc); - } - - } - - @Override - public List retrieveAll() { - final String sql = "select " + this.mixTaxonomyMapper.schema() + " order by id"; - return this.jdbcTemplate.query(sql, this.mixTaxonomyMapper); // NOSONAR - } - - @Override - public MixTaxonomyData retrieveOne(final Long id) { - final String sql = "select " + this.mixTaxonomyMapper.schema() + " where tx.id = ? "; - return this.jdbcTemplate.queryForObject(sql, this.mixTaxonomyMapper, new Object[] { id }); // NOSONAR - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java deleted file mode 100644 index 0ba7329530c..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/service/NamespaceReadPlatformServiceImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.service; - -import java.sql.ResultSet; -import java.sql.SQLException; -import org.apache.fineract.mix.data.NamespaceData; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - -public class NamespaceReadPlatformServiceImpl implements NamespaceReadPlatformService { - - private final JdbcTemplate jdbcTemplate; - private final NamespaceMapper namespaceMapper; - - public NamespaceReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - this.namespaceMapper = new NamespaceMapper(); - } - - private static final class NamespaceMapper implements RowMapper { - - public String schema() { - return "select id, prefix, url " + "from mix_xbrl_namespace"; - } - - @Override - public NamespaceData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final long id = rs.getLong("id"); - final String prefix = rs.getString("prefix"); - final String url = rs.getString("url"); - return new NamespaceData().setId(id).setPrefix(prefix).setUrl(url); - } - - } - - @Override - public NamespaceData retrieveNamespaceById(final Long id) { - final String sql = this.namespaceMapper.schema() + " where id= ? "; - - return this.jdbcTemplate.queryForObject(sql, this.namespaceMapper, new Object[] { id }); // NOSONAR - } - - @Override - public NamespaceData retrieveNamespaceByPrefix(final String prefix) { - final String sql = this.namespaceMapper.schema() + " where prefix = ? "; - - return this.jdbcTemplate.queryForObject(sql, this.namespaceMapper, new Object[] { prefix }); // NOSONAR - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java deleted file mode 100644 index b4d9f25d664..00000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/starter/MixConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.mix.starter; - -import org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; -import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformService; -import org.apache.fineract.mix.service.MixTaxonomyMappingReadPlatformServiceImpl; -import org.apache.fineract.mix.service.MixTaxonomyMappingWritePlatformService; -import org.apache.fineract.mix.service.MixTaxonomyMappingWritePlatformServiceImpl; -import org.apache.fineract.mix.service.MixTaxonomyReadPlatformService; -import org.apache.fineract.mix.service.MixTaxonomyReadPlatformServiceImpl; -import org.apache.fineract.mix.service.NamespaceReadPlatformService; -import org.apache.fineract.mix.service.NamespaceReadPlatformServiceImpl; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.core.JdbcTemplate; - -@Configuration -public class MixConfiguration { - - @Bean - @ConditionalOnMissingBean(MixTaxonomyMappingReadPlatformService.class) - public MixTaxonomyMappingReadPlatformService mixTaxonomyMappingReadPlatformService(JdbcTemplate jdbcTemplate) { - return new MixTaxonomyMappingReadPlatformServiceImpl(jdbcTemplate); - } - - @Bean - @ConditionalOnMissingBean(MixTaxonomyMappingWritePlatformService.class) - public MixTaxonomyMappingWritePlatformService mixTaxonomyMappingWritePlatformService(MixTaxonomyMappingRepository mappingRepository) { - return new MixTaxonomyMappingWritePlatformServiceImpl(mappingRepository); - } - - @Bean - @ConditionalOnMissingBean(MixTaxonomyReadPlatformService.class) - public MixTaxonomyReadPlatformService mixTaxonomyReadPlatformService(JdbcTemplate jdbcTemplate) { - return new MixTaxonomyReadPlatformServiceImpl(jdbcTemplate); - } - - @Bean - @ConditionalOnMissingBean(NamespaceReadPlatformService.class) - public NamespaceReadPlatformService namespaceReadPlatformService(JdbcTemplate jdbcTemplate) { - return new NamespaceReadPlatformServiceImpl(jdbcTemplate); - } - -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java index dc837b13db3..a75d5576d2a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/provisioning/service/ProvisioningCriteriaReadPlatformServiceImpl.java @@ -121,13 +121,13 @@ private List retrieveProvisioningDefinitions private static final class ProvisioningCriteriaDefinitionRowMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder() - .append("pc.id, pc.criteria_id, pc.category_id, mpc.category_name, pc.min_age, pc.max_age, ") - .append("pc.provision_percentage, pc.liability_account, pc.expense_account, lia.gl_code as liabilitycode, expe.gl_code as expensecode, ") - .append("lia.name as liabilityname, expe.name as expensename ").append("from m_provisioning_criteria_definition as pc ") - .append("LEFT JOIN acc_gl_account lia ON lia.id = pc.liability_account ") - .append("LEFT JOIN acc_gl_account expe ON expe.id = pc.expense_account ") - .append("LEFT JOIN m_provision_category mpc ON mpc.id = pc.category_id"); + private static final String PROVISIONING_CRITERIA_DEFINITION_SCHEMA = """ + pc.id, pc.criteria_id, pc.category_id, mpc.category_name, pc.min_age, pc.max_age, + pc.provision_percentage, pc.liability_account, pc.expense_account, lia.gl_code as liabilitycode, expe.gl_code as expensecode, + lia.name as liabilityname, expe.name as expensename from m_provisioning_criteria_definition as pc + LEFT JOIN acc_gl_account lia ON lia.id = pc.liability_account + LEFT JOIN acc_gl_account expe ON expe.id = pc.expense_account + LEFT JOIN m_provision_category mpc ON mpc.id = pc.category_id\s"""; @Override public ProvisioningCriteriaDefinitionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) @@ -153,7 +153,7 @@ public ProvisioningCriteriaDefinitionData mapRow(final ResultSet rs, @SuppressWa } public String schema() { - return sqlQuery.toString(); + return PROVISIONING_CRITERIA_DEFINITION_SCHEMA; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java index 26251e8b41d..8f1cb2e01ea 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/staff/api/StaffApiResource.java @@ -83,9 +83,10 @@ public class StaffApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Staff", description = "Returns the list of staff members.\n" + "\n" + "Example Requests:\n" + "\n" - + "staff\n\n\n\n" + "\n" + "Retrieve a Staff by status\n" + "\n" + "Returns the details of a Staff based on status.\n" + "\n" - + "By default it Returns all the ACTIVE Staff.\n" + "\n" + "If status=INACTIVE, then it returns all INACTIVE Staff.\n" + "\n" + @Operation(summary = "Retrieve Staff", operationId = "retrieveAllStaff", description = "Returns the list of staff members.\n" + "\n" + + "Example Requests:\n" + "\n" + "staff\n\n\n\n" + "\n" + "Retrieve a Staff by status\n" + "\n" + + "Returns the details of a Staff based on status.\n" + "\n" + "By default it Returns all the ACTIVE Staff.\n" + "\n" + + "If status=INACTIVE, then it returns all INACTIVE Staff.\n" + "\n" + "and for status=ALL, it Returns both ACTIVE and INACTIVE Staff.\n" + "\n" + "Example Requests:\n" + "\n" + "staff?status=active") public List retrieveAll(@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId, @@ -100,8 +101,8 @@ public List retrieveAll(@QueryParam("officeId") @Parameter(descriptio @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a staff member", description = "Creates a staff member.\n" + "\n" + "Mandatory Fields: \n" - + "officeId, firstname, lastname\n" + "\n" + "Optional Fields: \n" + "isLoanOfficer, isActive") + @Operation(summary = "Create a staff member", operationId = "createStaff", description = "Creates a staff member.\n" + "\n" + + "Mandatory Fields: \n" + "officeId, firstname, lastname\n" + "\n" + "Optional Fields: \n" + "isLoanOfficer, isActive") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = StaffRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = StaffApiResourceSwagger.CreateStaffResponse.class))) public CommandProcessingResult create(@Parameter(hidden = true) StaffRequest staffRequest) { @@ -114,8 +115,8 @@ public CommandProcessingResult create(@Parameter(hidden = true) StaffRequest sta @Path("{staffId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Staff Member", description = "Returns the details of a Staff Member.\n" + "\n" + "Example Requests:\n" - + "\n" + "staff/1") + @Operation(summary = "Retrieve a Staff Member", operationId = "retrieveOneStaff", description = "Returns the details of a Staff Member.\n" + + "\n" + "Example Requests:\n" + "\n" + "staff/1") public StaffData retrieveOne(@PathParam("staffId") @Parameter(description = "staffId") final Long staffId, @Context final UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java index 1ec2298d258..d58dd0bfa98 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/api/WorkingDaysApiResource.java @@ -19,26 +19,23 @@ package org.apache.fineract.organisation.workingdays.api; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import java.time.Instant; +import java.util.function.Supplier; import lombok.RequiredArgsConstructor; -import org.apache.fineract.commands.domain.CommandWrapper; -import org.apache.fineract.commands.service.CommandWrapperBuilder; -import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; -import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.command.core.CommandPipeline; +import org.apache.fineract.organisation.workingdays.command.WorkingDaysUpdateCommand; import org.apache.fineract.organisation.workingdays.data.WorkingDaysData; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService; import org.springframework.stereotype.Component; @@ -51,17 +48,15 @@ @RequiredArgsConstructor public class WorkingDaysApiResource { - private final DefaultToApiJsonSerializer toApiJsonSerializer; private final WorkingDaysReadPlatformService workingDaysReadPlatformService; - private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; - private final PlatformSecurityContext context; + private final WorkingDaysUpdateRequestValidator workingDaysUpdateRequestValidator; + private final CommandPipeline commandPipeline; @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "List Working days", description = "Example Requests:\n" + "\n" + "workingdays") public WorkingDaysData retrieveAll() { - this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); return this.workingDaysReadPlatformService.retrieve(); } @@ -70,15 +65,17 @@ public WorkingDaysData retrieveAll() { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Update a Working Day", description = "Mandatory Fields\n" + "recurrence,repaymentRescheduleType,extendTermForDailyRepayments,locale") - @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.PutWorkingDaysRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.PutWorkingDaysResponse.class))) - public String update(@Parameter(hidden = true) final String jsonRequestBody) { + public WorkingDaysUpdateResponse update(@Valid WorkingDaysUpdateRequest request) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateWorkingDays().withJson(jsonRequestBody).build(); + final var command = new WorkingDaysUpdateCommand(); - final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + command.setCommandId(System.currentTimeMillis()); + command.setCreatedAt(Instant.now()); + command.setPayload(request); - return this.toApiJsonSerializer.serialize(result); + final Supplier response = commandPipeline.send(command); + + return response.get(); } @GET @@ -87,10 +84,7 @@ public String update(@Parameter(hidden = true) final String jsonRequestBody) { @Produces({ MediaType.APPLICATION_JSON }) @Operation(summary = "Working Days Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for working days.\n" + "\n" + "Example Request:\n" + "\n" + "workingdays/template") - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingDaysApiResourceSwagger.GetWorkingDaysTemplateResponse.class))) public WorkingDaysData template() { - this.context.authenticatedUser().validateHasReadPermission(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); - return this.workingDaysReadPlatformService.repaymentRescheduleType(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java new file mode 100644 index 00000000000..d9fb6693165 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/command/WorkingDaysUpdateCommand.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.workingdays.command; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkingDaysUpdateCommand extends Command {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java index 8c3e365c18a..97416f3806a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDayValidator.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +//Not used @Component public class WorkingDayValidator { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java index ef2e62ab7b9..24d778779e8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysData.java @@ -21,12 +21,18 @@ import java.io.Serial; import java.io.Serializable; import java.util.Collection; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +@Builder @Data @NoArgsConstructor +@AllArgsConstructor +@FieldNameConstants public class WorkingDaysData implements Serializable { @Serial @@ -42,37 +48,7 @@ public class WorkingDaysData implements Serializable { private Boolean extendTermForRepaymentsOnHolidays; - // template date @SuppressWarnings("unused") private Collection repaymentRescheduleOptions; - public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType, Boolean extendTermForDailyRepayments, - Boolean extendTermForRepaymentsOnHolidays) { - this.id = id; - this.recurrence = recurrence; - this.repaymentRescheduleType = repaymentRescheduleType; - this.repaymentRescheduleOptions = null; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public WorkingDaysData(Long id, String recurrence, EnumOptionData repaymentRescheduleType, - Collection repaymentRescheduleOptions, Boolean extendTermForDailyRepayments, - Boolean extendTermForRepaymentsOnHolidays) { - this.id = id; - this.recurrence = recurrence; - this.repaymentRescheduleType = repaymentRescheduleType; - this.repaymentRescheduleOptions = repaymentRescheduleOptions; - this.extendTermForDailyRepayments = extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = extendTermForRepaymentsOnHolidays; - } - - public WorkingDaysData(WorkingDaysData data, Collection repaymentRescheduleOptions) { - this.id = data.id; - this.recurrence = data.recurrence; - this.repaymentRescheduleType = data.repaymentRescheduleType; - this.repaymentRescheduleOptions = repaymentRescheduleOptions; - this.extendTermForDailyRepayments = data.extendTermForDailyRepayments; - this.extendTermForRepaymentsOnHolidays = data.extendTermForRepaymentsOnHolidays; - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java new file mode 100644 index 00000000000..8eebb989b71 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequest.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.workingdays.data; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class WorkingDaysUpdateRequest implements Serializable { + + @Serial + public static final long serialVersionUID = 1L; + + private String recurrence; + + private Integer repaymentRescheduleType; + + private Boolean extendTermForDailyRepayments; + + private Boolean extendTermForRepaymentsOnHolidays; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java new file mode 100644 index 00000000000..b0a83178344 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateRequestValidator.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.workingdays.data; + +import java.util.ArrayList; +import java.util.List; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; +import org.springframework.stereotype.Component; + +@Component +public class WorkingDaysUpdateRequestValidator { + + public void validateForUpdate(final WorkingDaysUpdateRequest request) { + + List validationErrors = new ArrayList<>(); + DataValidatorBuilder validator = new DataValidatorBuilder(validationErrors) + .resource(WorkingDaysApiConstants.WORKING_DAYS_RESOURCE_NAME); + + // recurrence (mandatory) + String recurrence = request.getRecurrence(); + validator.reset().parameter(WorkingDaysApiConstants.recurrence).value(recurrence).notNull(); + + // repaymentRescheduleType (optional, but must be 1–4 if present) + validator.reset().parameter(WorkingDaysApiConstants.repayment_rescheduling_enum).value(request.getRepaymentRescheduleType()) + .ignoreIfNull().inMinMaxRange(1, 4); + + // extendTermForDailyRepayments (optional but must be boolean if provided) + validator.reset().parameter(WorkingDaysApiConstants.extendTermForDailyRepayments).value(request.getExtendTermForDailyRepayments()) + .ignoreIfNull().validateForBooleanValue(); + + // extendTermForRepaymentsOnHolidays (optional but must be boolean if provided) + validator.reset().parameter(WorkingDaysApiConstants.extendTermForRepaymentsOnHolidays) + .value(request.getExtendTermForRepaymentsOnHolidays()).ignoreIfNull().validateForBooleanValue(); + + throwExceptionIfValidationWarningsExist(validationErrors); + } + + private void throwExceptionIfValidationWarningsExist(final List validationErrors) { + if (!validationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(validationErrors); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java new file mode 100644 index 00000000000..725c3e9e77a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/data/WorkingDaysUpdateResponse.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.organisation.workingdays.data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WorkingDaysUpdateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + private Long resourceId; + private String recurrence; + private Integer repaymentRescheduleType; + private Boolean extendTermForDailyRepayments; + private Boolean extendTermForRepaymentsOnHolidays; + private Map changes; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java index 0f1fc6bad15..2528d818f8e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/handler/UpdateWorkingDaysCommandHandler.java @@ -18,29 +18,38 @@ */ package org.apache.fineract.organisation.workingdays.handler; -import org.apache.fineract.commands.annotation.CommandType; -import org.apache.fineract.commands.handler.NewCommandSourceHandler; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import java.util.Collections; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.command.core.Command; +import org.apache.fineract.command.core.CommandHandler; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateResponse; import org.apache.fineract.organisation.workingdays.service.WorkingDaysWritePlatformService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -@Service -@CommandType(entity = "WORKINGDAYS", action = "UPDATE") -public class UpdateWorkingDaysCommandHandler implements NewCommandSourceHandler { +@Slf4j +@Component +@RequiredArgsConstructor +public class UpdateWorkingDaysCommandHandler implements CommandHandler { private final WorkingDaysWritePlatformService workingDaysWritePlatformService; - @Autowired - public UpdateWorkingDaysCommandHandler(final WorkingDaysWritePlatformService workingDaysWritePlatformService) { - this.workingDaysWritePlatformService = workingDaysWritePlatformService; - } - @Transactional @Override - public CommandProcessingResult processCommand(JsonCommand command) { - return this.workingDaysWritePlatformService.updateWorkingDays(command); + public WorkingDaysUpdateResponse handle(Command command) { + WorkingDaysUpdateRequest request = command.getPayload(); + Map changes = this.workingDaysWritePlatformService.updateWorkingDays(request); + if (changes == null) { + changes = Collections.emptyMap(); + } + + return WorkingDaysUpdateResponse.builder().resourceId((Long) changes.get("resourceId")).changes(changes) + .recurrence(request.getRecurrence()).repaymentRescheduleType(request.getRepaymentRescheduleType()) + .extendTermForDailyRepayments(request.getExtendTermForDailyRepayments()) + .extendTermForRepaymentsOnHolidays(request.getExtendTermForRepaymentsOnHolidays()).build(); } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java index 59d6deeaa7d..1b4ecbafc29 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysReadPlatformServiceImpl.java @@ -65,7 +65,9 @@ public WorkingDaysData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowN final Boolean extendTermForDailyRepayments = rs.getBoolean("extendTermForDailyRepayments"); final Boolean extendTermForRepaymentsOnHolidays = rs.getBoolean("extendTermForRepaymentsOnHolidays"); - return new WorkingDaysData(id, recurrence, status, extendTermForDailyRepayments, extendTermForRepaymentsOnHolidays); + return WorkingDaysData.builder().id(id).recurrence(recurrence).repaymentRescheduleType(status) + .extendTermForDailyRepayments(extendTermForDailyRepayments) + .extendTermForRepaymentsOnHolidays(extendTermForRepaymentsOnHolidays).build(); } } @@ -77,7 +79,11 @@ public WorkingDaysData retrieve() { final String sql = " select " + rm.schema(); WorkingDaysData data = this.jdbcTemplate.queryForObject(sql, rm); // NOSONAR Collection repaymentRescheduleOptions = repaymentRescheduleTypeOptions(); - return new WorkingDaysData(data, repaymentRescheduleOptions); + return WorkingDaysData.builder().id(data.getId()).recurrence(data.getRecurrence()) + .repaymentRescheduleType(data.getRepaymentRescheduleType()) + .extendTermForDailyRepayments(data.getExtendTermForDailyRepayments()) + .extendTermForRepaymentsOnHolidays(data.getExtendTermForRepaymentsOnHolidays()) + .repaymentRescheduleOptions(repaymentRescheduleOptions).build(); } catch (final EmptyResultDataAccessException e) { throw new WorkingDaysNotFoundException(e); } @@ -86,7 +92,7 @@ public WorkingDaysData retrieve() { @Override public WorkingDaysData repaymentRescheduleType() { Collection repaymentRescheduleOptions = repaymentRescheduleTypeOptions(); - return new WorkingDaysData(null, null, null, repaymentRescheduleOptions, null, null); + return WorkingDaysData.builder().repaymentRescheduleOptions(repaymentRescheduleOptions).build(); } private Collection repaymentRescheduleTypeOptions() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java index dcdb3d1e40c..411991cd65f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformService.java @@ -18,10 +18,12 @@ */ package org.apache.fineract.organisation.workingdays.service; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import java.util.Map; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.springframework.transaction.annotation.Transactional; public interface WorkingDaysWritePlatformService { - CommandProcessingResult updateWorkingDays(JsonCommand command); + @Transactional + Map updateWorkingDays(WorkingDaysUpdateRequest request); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java index b0611c28bfb..c7e866bd6f7 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/service/WorkingDaysWritePlatformServiceJpaRepositoryImpl.java @@ -19,16 +19,15 @@ package org.apache.fineract.organisation.workingdays.service; import java.text.ParseException; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import lombok.RequiredArgsConstructor; import net.fortuna.ical4j.model.property.RRule; import net.fortuna.ical4j.validate.ValidationException; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; -import org.apache.fineract.organisation.workingdays.api.WorkingDaysApiConstants; -import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequest; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.springframework.transaction.annotation.Transactional; @@ -37,25 +36,27 @@ public class WorkingDaysWritePlatformServiceJpaRepositoryImpl implements WorkingDaysWritePlatformService { private final WorkingDaysRepositoryWrapper daysRepositoryWrapper; - private final WorkingDayValidator fromApiJsonDeserializer; + private final WorkingDaysUpdateRequestValidator validator; @Transactional @Override - public CommandProcessingResult updateWorkingDays(JsonCommand command) { + public Map updateWorkingDays(WorkingDaysUpdateRequest request) { String recurrence = ""; RRule rrule = null; try { - this.fromApiJsonDeserializer.validateForUpdate(command.json()); + this.validator.validateForUpdate(request); final WorkingDays workingDays = this.daysRepositoryWrapper.findOne(); - recurrence = command.stringValueOfParameterNamed(WorkingDaysApiConstants.recurrence); + recurrence = request.getRecurrence(); rrule = new RRule(recurrence); rrule.validate(); - Map changes = workingDays.update(command); + Map changes = update(workingDays, request); + // include the current WorkingDays resource id in the changes for response consumption + changes.put("resourceId", workingDays.getId()); this.daysRepositoryWrapper.saveAndFlush(workingDays); - return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(workingDays.getId()).with(changes) - .build(); + return changes; + } catch (final ValidationException e) { throw new PlatformDataIntegrityException("error.msg.invalid.recurring.rule", "The Recurring Rule value: " + recurrence + " is not valid.", "recurrence", recurrence, e); @@ -65,4 +66,32 @@ public CommandProcessingResult updateWorkingDays(JsonCommand command) { } } + public HashMap update(WorkingDays workingDays, WorkingDaysUpdateRequest request) { + HashMap changes = new HashMap<>(); + + if (!Objects.equals(request.getRecurrence(), workingDays.getRecurrence())) { + workingDays.setRecurrence(request.getRecurrence()); + changes.put("recurrence", request.getRecurrence()); + } + + Integer repaymentRescheduleType = request.getRepaymentRescheduleType(); + if (repaymentRescheduleType != null && !Objects.equals(repaymentRescheduleType, workingDays.getRepaymentReschedulingType())) { + workingDays.setRepaymentReschedulingType(repaymentRescheduleType); + changes.put("repaymentRescheduleType", repaymentRescheduleType); + } + + Boolean extendDaily = request.getExtendTermForDailyRepayments(); + if (extendDaily != null && !Objects.equals(extendDaily, workingDays.getExtendTermForDailyRepayments())) { + workingDays.setExtendTermForDailyRepayments(extendDaily); + changes.put("extendTermForDailyRepayments", extendDaily); + } + + Boolean extendHolidays = request.getExtendTermForRepaymentsOnHolidays(); + if (extendHolidays != null && !Objects.equals(extendHolidays, workingDays.getExtendTermForRepaymentsOnHolidays())) { + workingDays.setExtendTermForRepaymentsOnHolidays(extendHolidays); + changes.put("extendTermForRepaymentsOnHolidays", extendHolidays); + } + return changes; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java index 256909f5137..14f0e5c096d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/workingdays/starter/OrganisationWorkingDaysConfiguration.java @@ -18,7 +18,7 @@ */ package org.apache.fineract.organisation.workingdays.starter; -import org.apache.fineract.organisation.workingdays.data.WorkingDayValidator; +import org.apache.fineract.organisation.workingdays.data.WorkingDaysUpdateRequestValidator; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformService; import org.apache.fineract.organisation.workingdays.service.WorkingDaysReadPlatformServiceImpl; @@ -41,7 +41,7 @@ public WorkingDaysReadPlatformService workingDaysReadPlatformService(JdbcTemplat @Bean @ConditionalOnMissingBean(WorkingDaysWritePlatformService.class) public WorkingDaysWritePlatformService workingDaysWritePlatformService(WorkingDaysRepositoryWrapper daysRepositoryWrapper, - WorkingDayValidator fromApiJsonDeserializer) { - return new WorkingDaysWritePlatformServiceJpaRepositoryImpl(daysRepositoryWrapper, fromApiJsonDeserializer); + WorkingDaysUpdateRequestValidator validator) { + return new WorkingDaysWritePlatformServiceJpaRepositoryImpl(daysRepositoryWrapper, validator); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java index 07c137d3e4e..9dd9463692e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/api/AccountTransfersApiResource.java @@ -84,7 +84,7 @@ public AccountTransferData template(@BeanParam AccountTransSearchParam accountTr @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create new Transfer", description = "Ability to create new transfer of monetary funds from one account to another.") + @Operation(summary = "Create new Transfer", operationId = "createAccountTransfer", description = "Ability to create new transfer of monetary funds from one account to another.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = AccountTransferRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AccountTransfersApiResourceSwagger.PostAccountTransfersResponse.class))) public CommandProcessingResult create(@Parameter(hidden = true) AccountTransferRequest accountTransferRequest) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java index 552c2fc59e1..36fbe302c18 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/data/StandingInstructionDataValidator.java @@ -171,7 +171,7 @@ public void validateForCreate(final JsonCommand command) { baseDataValidator.reset().parameter(StandingInstructionApiConstants.nameParamName).value(name).notNull(); final Integer toAccountType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(toAccountTypeParamName, element); - if (toAccountType != null && PortfolioAccountType.fromInt(toAccountType).isSavingsAccount()) { + if (toAccountType != null && PortfolioAccountType.SAVINGS.equals(PortfolioAccountType.fromInt(toAccountType))) { baseDataValidator.reset().parameter(StandingInstructionApiConstants.instructionTypeParamName).value(standingInstructionType) .notNull().inMinMaxRange(1, 1); baseDataValidator.reset().parameter(StandingInstructionApiConstants.recurrenceTypeParamName).value(recurrenceType).notNull() @@ -188,11 +188,11 @@ public void validateForCreate(final JsonCommand command) { if (fromAccountType != null && toAccountType != null) { PortfolioAccountType fromPortfolioAccountType = PortfolioAccountType.fromInt(fromAccountType); PortfolioAccountType toPortfolioAccountType = PortfolioAccountType.fromInt(toAccountType); - if (accountTransferType.isAccountTransfer() - && (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isLoanAccount())) { + if (accountTransferType.isAccountTransfer() && (PortfolioAccountType.LOAN.equals(fromPortfolioAccountType) + || PortfolioAccountType.LOAN.equals(toPortfolioAccountType))) { errorCode = "not.account.transfer"; - } else if (accountTransferType.isLoanRepayment() - && (fromPortfolioAccountType.isLoanAccount() || toPortfolioAccountType.isSavingsAccount())) { + } else if (accountTransferType.isLoanRepayment() && (PortfolioAccountType.LOAN.equals(fromPortfolioAccountType) + || PortfolioAccountType.SAVINGS.equals(toPortfolioAccountType))) { errorCode = "not.loan.repayment"; } if (errorCode != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java index 0b4a9e06425..bb99a47ae0f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/ExecuteStandingInstructionsTasklet.java @@ -31,6 +31,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; +import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferDTO; import org.apache.fineract.portfolio.account.data.StandingInstructionData; import org.apache.fineract.portfolio.account.data.StandingInstructionDuesData; @@ -89,7 +90,7 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } BigDecimal transactionAmount = data.getAmount(); - if (data.getToAccountType().isLoanAccount() + if (PortfolioAccountType.LOAN.equals(data.getToAccountType()) && (recurrenceType.isDuesRecurrence() || (isDueForTransfer && instructionType.isDuesAmoutTransfer()))) { StandingInstructionDuesData standingInstructionDuesData = standingInstructionReadPlatformService .retriveLoanDuesData(data.getToAccount().getId()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/mapper/AccountTransfersMapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/mapper/AccountTransfersMapper.java index 4263b4e4d4f..2f241819d46 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/mapper/AccountTransfersMapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/mapper/AccountTransfersMapper.java @@ -37,50 +37,46 @@ @Component public final class AccountTransfersMapper implements RowMapper { - private final String schemaSql; - - public AccountTransfersMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("att.id as id, att.is_reversed as isReversed,"); - sqlBuilder.append("att.transaction_date as transferDate, att.amount as transferAmount,"); - sqlBuilder.append("att.description as transferDescription,"); - sqlBuilder.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,"); - sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); - sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); - sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); - sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); - sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); - sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); - sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); - sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,"); - sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,"); - sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,"); - sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,"); - sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType"); - sqlBuilder.append(" FROM m_account_transfer_transaction att "); - sqlBuilder.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id "); - sqlBuilder.append("join m_currency curr on curr.code = att.currency_code "); - sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id "); - sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id "); - sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id "); - sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id "); - sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); - sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); - sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); - sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); - sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id "); - - this.schemaSql = sqlBuilder.toString(); - } + private static final String ACCOUNT_TRANSFER_SCHEMA = """ + att.id as id, att.is_reversed as isReversed, + att.transaction_date as transferDate, att.amount as transferAmount, + att.description as transferDescription, + att.currency_code as currencyCode, att.currency_digits as currencyDigits, + att.currency_multiplesof as inMultiplesOf, + curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, + curr.display_symbol as currencyDisplaySymbol, + fromoff.id as fromOfficeId, fromoff.name as fromOfficeName, + tooff.id as toOfficeId, tooff.name as toOfficeName, + fromclient.id as fromClientId, fromclient.display_name as fromClientName, + toclient.id as toClientId, toclient.display_name as toClientName, + fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo, + fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo, + tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo, + toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo, + fromsavtran.id as fromSavingsAccountTransactionId, + fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType, + tosavtran.id as toSavingsAccountTransactionId, + tosavtran.transaction_type_enum as toSavingsAccountTransactionType + FROM m_account_transfer_transaction att + left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id + join m_currency curr on curr.code = att.currency_code + join m_office fromoff on fromoff.id = atd.from_office_id + join m_office tooff on tooff.id = atd.to_office_id + join m_client fromclient on fromclient.id = atd.from_client_id + join m_client toclient on toclient.id = atd.to_client_id + left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id + left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id + left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id + left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id + left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id + left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id + left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id + left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id\s"""; + + public AccountTransfersMapper() {} public String schema() { - return this.schemaSql; + return ACCOUNT_TRANSFER_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java index ff1a8536745..91e04a2bba5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -255,7 +255,7 @@ public Collection fetchPostInterestTransactionIdsWithPivotDate(final Long @Override public boolean isAccountTransfer(final Long transactionId, final PortfolioAccountType accountType) { final StringBuilder sql = new StringBuilder("select count(*) from m_account_transfer_transaction at where "); - if (accountType.isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(accountType)) { sql.append("at.from_loan_transaction_id=").append(transactionId).append(" or at.to_loan_transaction_id=").append(transactionId); } else { sql.append("at.from_savings_transaction_id=").append(transactionId).append(" or at.to_savings_transaction_id=") @@ -412,15 +412,16 @@ public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOffic @Override public BigDecimal getTotalTransactionAmount(Long accountId, Integer accountType, LocalDate transactionDate) { - StringBuilder sqlBuilder = new StringBuilder(" select sum(trans.amount) as totalTransactionAmount "); - sqlBuilder.append(" from m_account_transfer_details as det "); - sqlBuilder.append(" inner join m_account_transfer_transaction as trans "); - sqlBuilder.append(" on det.id = trans.account_transfer_details_id "); - sqlBuilder.append(" where trans.is_reversed = false "); - sqlBuilder.append(" and trans.transaction_date = ? "); - sqlBuilder.append(" and IF(1=?, det.from_loan_account_id = ?, det.from_savings_account_id = ?) "); - - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), BigDecimal.class, DATE_TIME_FORMATTER.format(transactionDate), - accountType, accountId, accountId); + final String sql = """ + select sum(trans.amount) as totalTransactionAmount + from m_account_transfer_details as det + inner join m_account_transfer_transaction as trans + on det.id = trans.account_transfer_details_id + where trans.is_reversed = false + and trans.transaction_date = ? + and IF(1=?, det.from_loan_account_id = ?, det.from_savings_account_id = ?)\s"""; + + return this.jdbcTemplate.queryForObject(sql, BigDecimal.class, DATE_TIME_FORMATTER.format(transactionDate), accountType, accountId, + accountId); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java index 7eea9969e62..836b9a17b81 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersWritePlatformServiceImpl.java @@ -198,10 +198,10 @@ public CommandProcessingResult create(final JsonCommand command) { final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferDetailId); - if (fromAccountType.isSavingsAccount()) { + if (PortfolioAccountType.SAVINGS.equals(fromAccountType)) { builder.withSavingsId(fromSavingsAccountId); } - if (fromAccountType.isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(fromAccountType)) { builder.withLoanId(fromLoanAccountId); } @@ -212,7 +212,7 @@ public CommandProcessingResult create(final JsonCommand command) { @Transactional public void reverseTransfersWithFromAccountType(final Long accountNumber, final PortfolioAccountType accountTypeId) { List accountTransfers = null; - if (accountTypeId.isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(accountTypeId)) { accountTransfers = this.accountTransferRepository.findByFromLoanId(accountNumber); } if (accountTransfers != null && !accountTransfers.isEmpty()) { @@ -226,7 +226,7 @@ public void reverseTransfersWithFromAccountType(final Long accountNumber, final public void reverseTransfersWithFromAccountTransactions(final Collection fromTransactionIds, final PortfolioAccountType accountTypeId) { List accountTransfers = new ArrayList<>(); - if (accountTypeId.isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(accountTypeId)) { List> partitions = Lists.partition(fromTransactionIds.stream().toList(), fineractProperties.getQuery().getInClauseParameterSizeLimit()); partitions.forEach(partition -> accountTransfers.addAll(this.accountTransferRepository.findByFromLoanTransactions(partition))); @@ -240,7 +240,7 @@ public void reverseTransfersWithFromAccountTransactions(final Collection f @Transactional public void reverseAllTransactions(final Long accountId, final PortfolioAccountType accountTypeId) { List accountTransfers = null; - if (accountTypeId.isLoanAccount()) { + if (PortfolioAccountType.LOAN.equals(accountTypeId)) { accountTransfers = this.accountTransferRepository.findAllByLoanId(accountId); } if (accountTransfers != null && !accountTransfers.isEmpty()) { @@ -493,16 +493,16 @@ public AccountTransferDetails repayLoanWithTopup(AccountTransferDTO accountTrans } private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount(); + return PortfolioAccountType.LOAN.equals(fromAccountType) && PortfolioAccountType.SAVINGS.equals(toAccountType); } private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount(); + return PortfolioAccountType.SAVINGS.equals(fromAccountType) && PortfolioAccountType.LOAN.equals(toAccountType); } private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount(); + return PortfolioAccountType.SAVINGS.equals(fromAccountType) && PortfolioAccountType.SAVINGS.equals(toAccountType); } @Override @@ -551,7 +551,7 @@ public CommandProcessingResult refundByTransfer(JsonCommand command) { final CommandProcessingResultBuilder builder = new CommandProcessingResultBuilder().withEntityId(transferTransactionId); - // if (fromAccountType.isSavingsAccount()) { + // if (PortfolioAccountType.SAVINGS.equals(fromAccountType)) { builder.withSavingsId(toSavingsAccountId); // } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java index b663de054e5..122ace18b2c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionHistoryReadPlatformServiceImpl.java @@ -107,10 +107,10 @@ public Page retrieveAll(StandingInstructionDTO s if (addAndCaluse) { sqlBuilder.append(" and "); } - if (accountType.isSavingsAccount()) { + if (PortfolioAccountType.SAVINGS.equals(accountType)) { sqlBuilder.append(" fromsavacc.id=? "); paramObj.add(standingInstructionDTO.fromAccount()); - } else if (accountType.isLoanAccount()) { + } else if (PortfolioAccountType.LOAN.equals(accountType)) { sqlBuilder.append(" fromloanacc.id=? "); paramObj.add(standingInstructionDTO.fromAccount()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java index 7e86160e27f..4f91636c684 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java @@ -295,10 +295,10 @@ public Page retrieveAll(final StandingInstructionDTO st if (addAndCaluse) { sqlBuilder.append(" and "); } - if (accountType.isSavingsAccount()) { + if (PortfolioAccountType.SAVINGS.equals(accountType)) { sqlBuilder.append(" fromsavacc.id=? "); paramObj.add(standingInstructionDTO.fromAccount()); - } else if (accountType.isLoanAccount()) { + } else if (PortfolioAccountType.LOAN.equals(accountType)) { sqlBuilder.append(" fromloanacc.id=? "); paramObj.add(standingInstructionDTO.fromAccount()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java index 5e364315eb5..a4f20321a1b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionWritePlatformServiceImpl.java @@ -110,16 +110,16 @@ private void handleDataIntegrityIssues(final JsonCommand command, Throwable real } private boolean isLoanToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isLoanAccount() && toAccountType.isSavingsAccount(); + return PortfolioAccountType.LOAN.equals(fromAccountType) && PortfolioAccountType.SAVINGS.equals(toAccountType); } private boolean isSavingsToLoanAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isSavingsAccount() && toAccountType.isLoanAccount(); + return PortfolioAccountType.SAVINGS.equals(fromAccountType) && PortfolioAccountType.LOAN.equals(toAccountType); } private boolean isSavingsToSavingsAccountTransfer(final PortfolioAccountType fromAccountType, final PortfolioAccountType toAccountType) { - return fromAccountType.isSavingsAccount() && toAccountType.isSavingsAccount(); + return PortfolioAccountType.SAVINGS.equals(fromAccountType) && PortfolioAccountType.SAVINGS.equals(toAccountType); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java index 3f9a284ec91..4bc9c1bc4d4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientTransactionsApiResource.java @@ -120,7 +120,7 @@ public String undoClientTransaction(@PathParam("clientId") @Parameter(descriptio @Path("external-id/{clientExternalId}/transactions") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Client Transactions", description = "The list capability of client transaction can support pagination." + @Operation(summary = "List Client Transactions", operationId = "retrieveAllClientTransactionsByClientExternalId", description = "The list capability of client transaction can support pagination." + "\n\n" + "Example Requests:\n\n" + "clients/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions\n\n" + "clients/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?offset=10&limit=50") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.GetClientsClientIdTransactionsResponse.class))) @@ -144,7 +144,7 @@ public String retrieveAllClientTransactions( @Path("external-id/{clientExternalId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Client Transaction", description = "Example Requests:\n" + @Operation(summary = "Retrieve a Client Transaction", operationId = "retrieveClientTransactionByClientExternalId", description = "Example Requests:\n" + "clients/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions/1\n" + "\n" + "\n" + "clients/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions/1?fields=id,officeName") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.GetClientsClientIdTransactionsTransactionIdResponse.class))) @@ -169,7 +169,7 @@ public String retrieveClientTransaction( @Path("external-id/{clientExternalId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo a Client Transaction", description = "Undoes a Client Transaction") + @Operation(summary = "Undo a Client Transaction", operationId = "undoClientTransactionByClientExternalId", description = "Undoes a Client Transaction") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.PostClientsClientIdTransactionsTransactionIdResponse.class))) public String undoClientTransaction( @PathParam("clientExternalId") @Parameter(description = "clientExternalId") final String clientExternalId, @@ -191,7 +191,7 @@ public String undoClientTransaction( @Path("{clientId}/transactions/external-id/{transactionExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Client Transaction", description = "Example Requests:\n" + @Operation(summary = "Retrieve a Client Transaction", operationId = "retrieveClientTransactionByTransactionExternalId", description = "Example Requests:\n" + "clients/1/transactions/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854\n" + "\n" + "\n" + "clients/1/transactions/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854?fields=id,officeName") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.GetClientsClientIdTransactionsTransactionIdResponse.class))) @@ -247,7 +247,7 @@ public String retrieveClientTransaction( @Path("{clientId}/transactions/external-id/{transactionExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo a Client Transaction", description = "Undoes a Client Transaction") + @Operation(summary = "Undo a Client Transaction", operationId = "undoClientTransactionByTransactionExternalId", description = "Undoes a Client Transaction") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientTransactionsApiResourceSwagger.PostClientsClientIdTransactionsTransactionIdResponse.class))) public String undoClientTransaction(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId, @PathParam("transactionExternalId") @Parameter(description = "transactionExternalId") final String transactionExternalId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java index 37c22235790..fd434db3d7a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientsApiResource.java @@ -132,7 +132,7 @@ public String retrieveTemplate(@Context final UriInfo uriInfo, @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Clients", description = "The list capability of clients can support pagination and sorting.\n\n" + @Operation(summary = "List Clients", operationId = "retrieveAllClients", description = "The list capability of clients can support pagination and sorting.\n\n" + "Example Requests:\n" + "\n" + "clients\n" + "\n" + "clients?fields=displayName,officeName,timeline\n" + "\n" + "clients?offset=10&limit=50\n" + "\n" + "clients?orderBy=displayName&sortOrder=DESC") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.GetClientsResponse.class))) @@ -159,8 +159,8 @@ public String retrieveAll(@Context final UriInfo uriInfo, @Path("{clientId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Client", description = "Example Requests:\n" + "\n" + "clients/1\n" + "\n" + "\n" - + "clients/1?template=true\n" + "\n" + "\n" + "clients/1?fields=id,displayName,officeName") + @Operation(summary = "Retrieve a Client", operationId = "retrieveOneClient", description = "Example Requests:\n" + "\n" + "clients/1\n" + + "\n" + "\n" + "clients/1?template=true\n" + "\n" + "\n" + "clients/1?fields=id,displayName,officeName") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.GetClientsClientIdResponse.class))) public String retrieveOne(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId, @Context final UriInfo uriInfo, @@ -171,7 +171,7 @@ public String retrieveOne(@PathParam("clientId") @Parameter(description = "clien @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Client", description = "Note:\n\n" + @Operation(summary = "Create a Client", operationId = "createClient", description = "Note:\n\n" + "1. You can enter either:firstname/middlename/lastname - for a person (middlename is optional) OR fullname - for a business or organisation (or person known by one name).\n" + "\n" + "2.If address is enable(enable-address=true), then additional field called address has to be passed.\n\n" + "Mandatory Fields: firstname and lastname OR fullname, officeId, active=true and activationDate OR active=false, if(address enabled) address\n\n" @@ -315,8 +315,9 @@ public String retrieveTransferTemplate(@PathParam("clientId") final Long clientI @GET @Path("/external-id/{externalId}") @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Client by External Id", description = "Example Requests:\n" + "\n" + "clients/123-456\n" + "\n" + "\n" - + "clients/123-456?template=true\n" + "\n" + "\n" + "clients/123-456?fields=id,displayName,officeName") + @Operation(summary = "Retrieve a Client by External Id", operationId = "retrieveOneClientByExternalId", description = "Example Requests:\n" + + "\n" + "clients/123-456\n" + "\n" + "\n" + "clients/123-456?template=true\n" + "\n" + "\n" + + "clients/123-456?fields=id,displayName,officeName") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.GetClientsClientIdResponse.class))) public String retrieveOne(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, @Context final UriInfo uriInfo, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java index 35821ad6bbf..6afb51e3b1b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanChargesApiResource.java @@ -108,8 +108,8 @@ public String retrieveAllLoanCharges(@PathParam("loanId") @Parameter(description @Path("external-id/{loanExternalId}/charges") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Loan Charges", description = "It lists all the Loan Charges specific to a Loan \n\n" + "Example Requests:\n" - + "\n" + "loans/1/charges\n" + "\n" + "\n" + "loans/1/charges?fields=name,amountOrPercentage") + @Operation(summary = "List Loan Charges", operationId = "retrieveAllLoanChargesByLoanExternalId", description = "It lists all the Loan Charges specific to a Loan \n\n" + + "Example Requests:\n" + "\n" + "loans/1/charges\n" + "\n" + "\n" + "loans/1/charges?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class)))) public String retrieveAllLoanCharges( @PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -122,7 +122,7 @@ public String retrieveAllLoanCharges( @Path("{loanId}/charges/template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Loan Charges Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Loan Charges Template", operationId = "retrieveTemplateLoanCharge", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loans/1/charges/template\n" + "\n") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesTemplateResponse.class))) public String retrieveTemplate(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @@ -135,7 +135,7 @@ public String retrieveTemplate(@PathParam("loanId") @Parameter(description = "lo @Path("external-id/{loanExternalId}/charges/template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Loan Charges Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Loan Charges Template", operationId = "retrieveTemplateLoanChargeByLoanExternalId", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loans/1/charges/template\n" + "\n") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesTemplateResponse.class))) public String retrieveTemplate(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -161,7 +161,7 @@ public String retrieveLoanCharge(@PathParam("loanId") @Parameter(description = " @Path("{loanId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge External ID" + @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveLoanChargeByChargeExternalId", description = "Retrieves Loan Charge according to the Loan ID and Loan Charge External ID" + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/external-id/1?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) @@ -176,7 +176,7 @@ public String retrieveLoanCharge(@PathParam("loanId") @Parameter(description = " @Path("external-id/{loanExternalId}/charges/{loanChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan external ID and Loan Charge ID" + @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveLoanChargeByLoanExternalId", description = "Retrieves Loan Charge according to the Loan external ID and Loan Charge ID" + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) public String retrieveLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -189,7 +189,7 @@ public String retrieveLoanCharge(@PathParam("loanExternalId") @Parameter(descrip @Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Loan Charge", description = "Retrieves Loan Charge according to the Loan External ID and Loan Charge External ID" + @Operation(summary = "Retrieve a Loan Charge", operationId = "retrieveLoanChargeByLoanAndChargeExternalId", description = "Retrieves Loan Charge according to the Loan External ID and Loan Charge External ID" + "Example Requests:\n" + "\n" + "/loans/1/charges/1\n" + "\n" + "\n" + "/loans/1/charges/1?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.GetLoansLoanIdChargesChargeIdResponse.class))) public String retrieveLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -217,7 +217,7 @@ public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "l @Path("external-id/{loanExternalId}/charges") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Loan Charge (no command provided) or Pay a charge (command=pay)", description = "Creates a Loan Charge | Pay a Loan Charge") + @Operation(summary = "Create a Loan Charge (no command provided) or Pay a charge (command=pay)", operationId = "executeLoanChargeByLoanExternalId", description = "Creates a Loan Charge | Pay a Loan Charge") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesResponse.class))) public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -231,7 +231,7 @@ public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(descript @Path("{loanId}/charges/{loanChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") + @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", operationId = "executeLoanChargeOnExistingCharge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @@ -246,7 +246,7 @@ public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "l @Path("{loanId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") + @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", operationId = "executeLoanChargeByChargeExternalId", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @@ -261,7 +261,7 @@ public String executeLoanCharge(@PathParam("loanId") @Parameter(description = "l @Path("external-id/{loanExternalId}/charges/{loanChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") + @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", operationId = "executeLoanChargeByLoanExternalIdOnExistingCharge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -276,7 +276,7 @@ public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(descript @Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") + @Operation(summary = "Pay / Waive / Adjustment for Loan Charge", operationId = "executeLoanChargeByLoanAndChargeExternalId", description = "Loan Charge will be paid if the loan is linked with a savings account | Waive Loan Charge | Add Charge Adjustment") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PostLoansLoanIdChargesChargeIdResponse.class))) public String executeLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -305,7 +305,7 @@ public String updateLoanCharge(@PathParam("loanId") @Parameter(description = "lo @Path("{loanId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") + @Operation(summary = "Update a Loan Charge", operationId = "updateLoanChargeByChargeExternalId", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) public String updateLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @@ -319,7 +319,7 @@ public String updateLoanCharge(@PathParam("loanId") @Parameter(description = "lo @Path("external-id/{loanExternalId}/charges/{loanChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") + @Operation(summary = "Update a Loan Charge", operationId = "updateLoanChargeByLoanExternalId", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) public String updateLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -333,7 +333,7 @@ public String updateLoanCharge(@PathParam("loanExternalId") @Parameter(descripti @Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Loan Charge", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") + @Operation(summary = "Update a Loan Charge", operationId = "updateLoanChargeByLoanAndChargeExternalId", description = "Currently Loan Charges may be updated only if the Loan is not yet approved") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.PutLoansLoanIdChargesChargeIdResponse.class))) public String updateLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @@ -359,7 +359,7 @@ public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "lo @Path("{loanId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") + @Operation(summary = "Delete a Loan Charge", operationId = "deleteLoanChargeByChargeExternalId", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId, @PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) { @@ -371,7 +371,7 @@ public String deleteLoanCharge(@PathParam("loanId") @Parameter(description = "lo @Path("external-id/{loanExternalId}/charges/{loanChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") + @Operation(summary = "Delete a Loan Charge", operationId = "deleteLoanChargeByLoanExternalId", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) public String deleteLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @PathParam("loanChargeId") @Parameter(description = "loanChargeId") final Long loanChargeId) { @@ -383,7 +383,7 @@ public String deleteLoanCharge(@PathParam("loanExternalId") @Parameter(descripti @Path("external-id/{loanExternalId}/charges/external-id/{loanChargeExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Loan Charge", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") + @Operation(summary = "Delete a Loan Charge", operationId = "deleteLoanChargeByLoanAndChargeExternalId", description = "Note: Currently, A Loan Charge may only be removed from Loans that are not yet approved.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanChargesApiResourceSwagger.DeleteLoansLoanIdChargesChargeIdResponse.class))) public String deleteLoanCharge(@PathParam("loanExternalId") @Parameter(description = "loanExternalId") final String loanExternalId, @PathParam("loanChargeExternalId") @Parameter(description = "loanChargeExternalId") final String loanChargeExternalId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 112c18b6b97..2b5bfb5b0c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -150,7 +150,7 @@ public String retrieveTransactionTemplate(@PathParam("loanId") @Parameter(descri @Path("external-id/{loanExternalId}/transactions/template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Loan Transaction Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Loan Transaction Template", operationId = "retrieveTransactionTemplateByLoanExternalId", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n" + "loans/1/transactions/template?command=repayment" + "loans/1/transactions/template?command=merchantIssuedRefund" + "loans/1/transactions/template?command=payoutRefund" + "loans/1/transactions/template?command=goodwillCredit" + "\n" @@ -312,7 +312,7 @@ public String executeLoanTransaction(@PathParam("loanId") @Parameter(description @Path("external-id/{loanExternalId}/transactions") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Significant Loan Transactions", description = "This API covers the major loan transaction functionality\n\n" + @Operation(summary = "Significant Loan Transactions", operationId = "executeLoanTransactionByLoanExternalId", description = "This API covers the major loan transaction functionality\n\n" + "Example Requests:\n\n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=repayment" + " | Make a Repayment | \n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854/transactions?command=merchantIssuedRefund" @@ -365,7 +365,7 @@ public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description @Path("external-id/{loanExternalId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + @Operation(summary = "Adjust a Transaction", operationId = "adjustLoanTransactionByLoanExternalId", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + "Mandatory Fields: transactionDate, transactionAmount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class))) @ApiResponses({ @@ -383,7 +383,7 @@ public String adjustLoanTransaction( @Path("{loanId}/transactions/external-id/{externalTransactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + @Operation(summary = "Adjust a Transaction", operationId = "adjustLoanTransactionByTransactionExternalId", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + "Mandatory Fields: transactionDate, transactionAmount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class))) @ApiResponses({ @@ -400,7 +400,7 @@ public String adjustLoanTransaction(@PathParam("loanId") @Parameter(description @Path("external-id/{loanExternalId}/transactions/external-id/{externalTransactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Adjust a Transaction", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + @Operation(summary = "Adjust a Transaction", operationId = "adjustLoanTransactionByLoanAndTransactionExternalId", description = "Note: there is no need to specify command={transactionType} parameter.\n\n" + "Mandatory Fields: transactionDate, transactionAmount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PostLoansLoanIdTransactionsTransactionIdRequest.class))) @ApiResponses({ @@ -432,7 +432,7 @@ public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loa @Path("external-id/{loanExternalId}/transactions/{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction") + @Operation(summary = "Undo a Waive Charge Transaction", operationId = "undoWaiveChargeByLoanExternalId", description = "Undo a Waive Charge Transaction") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) }) @@ -447,7 +447,7 @@ public String undoWaiveCharge( @Path("{loanId}/transactions/external-id/{transactionExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction") + @Operation(summary = "Undo a Waive Charge Transaction", operationId = "undoWaiveChargeByTransactionExternalId", description = "Undo a Waive Charge Transaction") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) }) @@ -461,7 +461,7 @@ public String undoWaiveCharge(@PathParam("loanId") @Parameter(description = "loa @Path("external-id/{loanExternalId}/transactions/external-id/{transactionExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo a Waive Charge Transaction", description = "Undo a Waive Charge Transaction") + @Operation(summary = "Undo a Waive Charge Transaction", operationId = "undoWaiveChargeByLoanAndTransactionExternalId", description = "Undo a Waive Charge Transaction") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanTransactionsApiResourceSwagger.PutChargeTransactionChangesResponse.class))) }) @@ -801,7 +801,7 @@ public LoanScheduleData previewReAgeSchedule(@PathParam("loanId") @Parameter(des @GET @Path("external-id/{loanExternalId}/transactions/reage-preview") @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Preview Re-Age Schedule", description = "Generates a preview of the re-aged loan schedule based on the provided parameters without creating any transactions or modifying the loan.") + @Operation(summary = "Preview Re-Age Schedule", operationId = "previewReAgeScheduleByLoanExternalId", description = "Generates a preview of the re-aged loan schedule based on the provided parameters without creating any transactions or modifying the loan.") public LoanScheduleData previewReAgeSchedule( @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId, @Valid @BeanParam final ReAgePreviewRequest reAgePreviewRequest) { @@ -823,7 +823,7 @@ public LoanScheduleData previewReAmortizationSchedule( @GET @Path("external-id/{loanExternalId}/transactions/reamortization-preview") @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Preview Re-amortized Schedule", description = "Generates a preview of the re-amortized loan schedule based on the provided parameters without creating any transactions or modifying the loan.") + @Operation(summary = "Preview Re-amortized Schedule", operationId = "previewReAmortizationScheduleByLoanExternalId", description = "Generates a preview of the re-amortized loan schedule based on the provided parameters without creating any transactions or modifying the loan.") public LoanScheduleData previewReAmortizationSchedule( @PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId, @Valid @BeanParam final ReAmortizationPreviewRequest reAmortizationPreviewRequest) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index bf68bede579..d9a362dfa78 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -483,7 +483,7 @@ public String retrieveLoan(@PathParam("loanId") @Parameter(description = "loanId @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Loans", description = "The list capability of loans can support pagination and sorting.\n" + @Operation(summary = "List Loans", operationId = "retrieveAllLoans", description = "The list capability of loans can support pagination and sorting.\n" + "Example Requests:\n" + "\n" + "loans\n" + "\n" + "loans?fields=accountNo\n" + "\n" + "loans?offset=10&limit=50\n" + "\n" + "loans?orderBy=accountNo&sortOrder=DESC") @ApiResponses({ @@ -748,6 +748,7 @@ public String getDelinquencyTagHistory(@PathParam("loanId") @Parameter(descripti @Path("external-id/{loanExternalId}/template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Loan Approval Template", operationId = "retrieveApprovalTemplateByExternalId") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.GetLoansApprovalTemplateResponse.class))) }) public String retrieveApprovalTemplate( @@ -761,7 +762,7 @@ public String retrieveApprovalTemplate( @Path("external-id/{loanExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Loan", description = "Note: template=true parameter doesn't apply to this resource." + @Operation(summary = "Retrieve a Loan", operationId = "retrieveLoanByExternalId", description = "Note: template=true parameter doesn't apply to this resource." + "Example Requests:\n" + "\n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854\n" + "\n" + "\n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854?fields=id,principal,annualInterestRate\n" + "\n" + "\n" + "loans/external-id/7dd80a7c-ycba-a446-t378-91eb6f53e854?associations=all\n" + "\n" @@ -784,7 +785,7 @@ public String retrieveLoan( @Path("external-id/{loanExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a loan application", description = "Loan application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method.") + @Operation(summary = "Modify a loan application", operationId = "modifyLoanApplicationByExternalId", description = "Loan application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansLoanIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansLoanIdResponse.class))) }) @@ -799,7 +800,7 @@ public String modifyLoanApplication( @Path("external-id/{loanExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Loan Application", description = "Note: Only loans in \"Submitted and awaiting approval\" status can be deleted.") + @Operation(summary = "Delete a Loan Application", operationId = "deleteLoanApplicationByExternalId", description = "Note: Only loans in \"Submitted and awaiting approval\" status can be deleted.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.DeleteLoansLoanIdResponse.class))) }) public String deleteLoanApplication( @@ -811,7 +812,7 @@ public String deleteLoanApplication( @Path("external-id/{loanExternalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Approve Loan Application | Recover Loan Guarantee | Undo Loan Application Approval | Assign a Loan Officer | Unassign a Loan Officer | Reject Loan Application | Applicant Withdraws from Loan Application | Disburse Loan Disburse Loan To Savings Account | Undo Loan Disbursal", description = "Approve Loan Application:\n" + @Operation(summary = "Approve Loan Application | Recover Loan Guarantee | Undo Loan Application Approval | Assign a Loan Officer | Unassign a Loan Officer | Reject Loan Application | Applicant Withdraws from Loan Application | Disburse Loan Disburse Loan To Savings Account | Undo Loan Disbursal", operationId = "stateTransitionsByExternalId", description = "Approve Loan Application:\n" + "Mandatory Fields: approvedOnDate\n" + "Optional Fields: approvedLoanAmount and expectedDisbursementDate\n" + "Approves the loan application\n\n" + "Recover Loan Guarantee:\n" + "Recovers the loan guarantee\n\n" + "Undo Loan Application Approval:\n" + "Undoes the Loan Application Approval\n\n" + "Assign a Loan Officer:\n" @@ -837,7 +838,7 @@ public String stateTransitions( @Path("external-id/{loanExternalId}/delinquencytags") @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON }) @Produces(MediaType.APPLICATION_JSON) - @Operation(summary = "Retrieve the Loan Delinquency Tag history using the Loan Id", description = "") + @Operation(summary = "Retrieve the Loan Delinquency Tag history using the Loan Id", operationId = "getDelinquencyTagHistoryByExternalId", description = "") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyTagHistoryResponse.class)))) }) public String getDelinquencyTagHistory( @@ -862,7 +863,7 @@ public String getLoanDelinquencyActions(@PathParam("loanId") @Parameter(descript @Path("external-id/{loanExternalId}/delinquency-actions") @Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON }) @Produces(MediaType.APPLICATION_JSON) - @Operation(summary = "Retrieve delinquency actions related to the loan", description = "") + @Operation(summary = "Retrieve delinquency actions related to the loan", operationId = "getLoanDelinquencyActionsByExternalId", description = "") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyActionsResponse.class)))) }) public String getLoanDelinquencyActions( @@ -888,7 +889,7 @@ public String createLoanDelinquencyAction(@PathParam("loanId") @Parameter(descri @Path("external-id/{loanExternalId}/delinquency-actions") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Operation(summary = "Adds a new delinquency action for a loan", description = "") + @Operation(summary = "Adds a new delinquency action for a loan", operationId = "createLoanDelinquencyActionByExternalId", description = "") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionResponse.class))) }) @@ -965,7 +966,7 @@ public CommandProcessingResult modifyLoanAvailableDisbursementAmount( @Path("external-id/{loanExternalId}/available-disbursement-amount") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Operation(summary = "Modifies the available disbursement amount of the loan", description = "Modifies the available disbursement amount of the loan, this indirectly modifies the approved amount that can be disbursed on the loan") + @Operation(summary = "Modifies the available disbursement amount of the loan", operationId = "modifyLoanAvailableDisbursementAmountByExternalId", description = "Modifies the available disbursement amount of the loan, this indirectly modifies the approved amount that can be disbursed on the loan") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoansApiResourceSwagger.PutLoansAvailableDisbursementAmountResponse.class))) }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 45bb2367fbb..723a9b25c0c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -1610,7 +1610,7 @@ private PutLoansLoanIdRequest() {} public Long clientId; @Schema(example = "individual") public String loanType; - public List charges; + public List charges; public List collateral; public List disbursementData; @Schema(example = "false", description = "Allow full term length for each tranche disbursement") @@ -1628,9 +1628,9 @@ private PutLoansLoanIdRequest() {} @Schema(example = "false") public Boolean interestRecognitionOnDisbursementDate; - static final class PutLoansLoanIdChanges { + static final class PutLoansLoanIdChargeData { - private PutLoansLoanIdChanges() {} + private PutLoansLoanIdChargeData() {} @Schema(example = "dd MMMM yyyy") public String dateFormat; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java index 4bdd36bea3a..0aa9a33bdb8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/data/GuarantorData.java @@ -207,4 +207,8 @@ public boolean isExistingClient() { public boolean isStaffMember() { return GuarantorType.STAFF.getValue().equals(this.guarantorType.getId().intValue()); } + + public boolean isExistingGroup() { + return GuarantorType.GROUP.getValue().equals(this.guarantorType.getId().intValue()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java index 8b734bb764e..e7cd28325aa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/domain/Guarantor.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -42,17 +43,21 @@ @Table(name = "m_guarantor") public class Guarantor extends AbstractPersistableCustom { + @Getter @ManyToOne @JoinColumn(name = "loan_id", nullable = false) private Loan loan; + @Getter @ManyToOne @JoinColumn(name = "client_reln_cv_id", nullable = false) private CodeValue clientRelationshipType; + @Getter @Column(name = "type_enum", nullable = false) private Integer gurantorType; + @Getter @Column(name = "entity_id") private Long entityId; @@ -95,6 +100,7 @@ public class Guarantor extends AbstractPersistableCustom { @Column(name = "is_active", nullable = false) private boolean active; + @Getter @OneToMany(cascade = CascadeType.ALL, mappedBy = "guarantor", orphanRemoval = true, fetch = FetchType.EAGER) private List guarantorFundDetails = new ArrayList<>(); @@ -188,6 +194,10 @@ public boolean isExistingEmployee() { return GuarantorType.STAFF.getValue().equals(this.gurantorType); } + public boolean isExistingGroup() { + return GuarantorType.GROUP.getValue().equals(this.gurantorType); + } + public boolean isExternalGuarantor() { return GuarantorType.EXTERNAL.getValue().equals(this.gurantorType); } @@ -202,7 +212,6 @@ private void handlePropertyUpdate(final JsonCommand command, final Map getGuarantorFundDetails() { - return this.guarantorFundDetails; - } - public boolean hasGuarantor(Long savingsId) { if (savingsId == null) { return false; @@ -354,10 +341,6 @@ public boolean hasGuarantor(Long savingsId) { } public boolean isSelfGuarantee() { - boolean isSelf = false; - if (isExistingCustomer() && getEntityId().equals(getClientId())) { - isSelf = true; - } - return isSelf; + return isExistingCustomer() && getEntityId().equals(getClientId()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java index 4113a842d4b..82d532ad75b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorDomainServiceImpl.java @@ -390,7 +390,7 @@ private void releaseGuarantorFunds(final LoanTransaction loanTransaction) { if (guarantor.isSelfGuarantee()) { selfGuarantorList.add(guarantorFundingDetails); selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining()); - } else if (guarantor.isExistingCustomer()) { + } else if (guarantor.isExistingCustomer() || guarantor.isExistingGroup()) { externalGuarantorList.add(guarantorFundingDetails); guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java index 7e71bd14c14..9773a0e5734 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorWritePlatformServiceJpaRepositoryIImpl.java @@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociations; import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; +import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants; @@ -70,6 +71,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto private final ClientRepositoryWrapper clientRepositoryWrapper; private final StaffRepositoryWrapper staffRepositoryWrapper; + private final GroupRepositoryWrapper groupRepositoryWrapper; private final LoanRepositoryWrapper loanRepositoryWrapper; private final GuarantorRepository guarantorRepository; private final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer; @@ -81,11 +83,13 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto @Autowired public GuarantorWritePlatformServiceJpaRepositoryIImpl(final LoanRepositoryWrapper loanRepositoryWrapper, final GuarantorRepository guarantorRepository, final ClientRepositoryWrapper clientRepositoryWrapper, - final StaffRepositoryWrapper staffRepositoryWrapper, final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer, + final StaffRepositoryWrapper staffRepositoryWrapper, final GroupRepositoryWrapper groupRepositoryWrapper, + final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer, final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler, final AccountAssociationsRepository accountAssociationsRepository, final GuarantorDomainService guarantorDomainService) { this.loanRepositoryWrapper = loanRepositoryWrapper; this.clientRepositoryWrapper = clientRepositoryWrapper; + this.groupRepositoryWrapper = groupRepositoryWrapper; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.guarantorRepository = guarantorRepository; this.staffRepositoryWrapper = staffRepositoryWrapper; @@ -148,6 +152,8 @@ private CommandProcessingResult createGuarantor(final Loan loan, final JsonComma String defaultUserMessage = null; if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) { defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName(); + } else if (guarantorTypeId.equals(GuarantorType.GROUP.getValue())) { + defaultUserMessage = this.groupRepositoryWrapper.findOneWithNotFoundDetection(entityId).getName(); } else { defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName(); } @@ -225,12 +231,19 @@ public CommandProcessingResult updateGuarantor(final Long loanId, final Long gua final List existGuarantorList = this.guarantorRepository.findByLoan(loan); final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId(); final GuarantorType guarantorType = GuarantorType.fromInt(guarantorTypeId); - if (guarantorType.isCustomer() || guarantorType.isStaff()) { + if (guarantorType.isCustomer() || guarantorType.isStaff() || guarantorType.isGroup()) { final Long entityId = guarantorCommand.getEntityId(); for (final Guarantor guarantor : existGuarantorList) { if (guarantor.getEntityId().equals(entityId) && guarantor.getGurantorType().equals(guarantorTypeId) && !guarantorForUpdate.getId().equals(guarantor.getId())) { - String defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName(); + String defaultUserMessage = null; + if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) { + defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName(); + } else if (guarantorTypeId.equals(GuarantorType.GROUP.getValue())) { + defaultUserMessage = this.groupRepositoryWrapper.findOneWithNotFoundDetection(entityId).getName(); + } else { + defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName(); + } defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan"; final String action = loan.client() != null ? "client.guarantor" : "group.guarantor"; throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId, loanId); @@ -336,6 +349,9 @@ private void validateGuarantorBusinessRules(final Guarantor guarantor) { } else if (guarantor.isExistingEmployee()) { this.staffRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId()); + } else if (guarantor.isExistingGroup()) { + // check group exists + this.groupRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java index fe2fa363e72..df3f4558a75 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDisbursementValidator.java @@ -34,24 +34,20 @@ public final class LoanDisbursementValidator { private final LoanApplicationValidator loanApplicationValidator; - public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final BigDecimal disbursedAmount, - final BigDecimal totalDisbursed) { + public void compareDisbursedToApprovedOrProposedPrincipal(final Loan loan, final BigDecimal totalDisbursed) { final BigDecimal totalCapitalizedIncome = loan.getSummary().getTotalCapitalizedIncome(); final BigDecimal totalCapitalizedIncomeAdjustment = MathUtil.nullToZero(loan.getSummary().getTotalCapitalizedIncomeAdjustment()); final BigDecimal netCapitalizedIncome = totalCapitalizedIncome.subtract(totalCapitalizedIncomeAdjustment); - if (loan.loanProduct().isDisallowExpectedDisbursements() && loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { + if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { + // Validate total disbursed amount (after this transaction) against max allowed validateOverMaximumAmount(loan, totalDisbursed, netCapitalizedIncome); } else { - if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) { - validateOverMaximumAmount(loan, disbursedAmount, netCapitalizedIncome); - } else { - if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0) - || (totalDisbursed.add(netCapitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) { - final String errorMsg = "Loan can't be disbursed, disburse amount is exceeding approved principal."; - throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed, - loan.getApprovedPrincipal()); - } + if ((totalDisbursed.compareTo(loan.getApprovedPrincipal()) > 0) + || (totalDisbursed.add(netCapitalizedIncome).compareTo(loan.getApprovedPrincipal()) > 0)) { + final String errorMsg = "Loan can't be disbursed, disburse amount is exceeding approved principal."; + throw new LoanDisbursalException(errorMsg, "disburse.amount.must.be.less.than.approved.principal", totalDisbursed, + loan.getApprovedPrincipal()); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java index 65c55ee8c4a..43132cc3286 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java @@ -162,8 +162,9 @@ public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, validateLoanClientIsActive(loan); validateLoanGroupIsActive(loan); - final BigDecimal disbursedAmount = loan.getSummary().getTotalPrincipalDisbursed(); - loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, principal, disbursedAmount); + final BigDecimal totalDisbursedAmount = principal != null ? loan.getDisbursedAmount().add(principal) + : loan.getDisbursedAmount(); + loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, totalDisbursedAmount); if (loan.isChargedOff()) { throw new GeneralPlatformDomainRuleException("error.msg.loan.disbursal.not.allowed.on.charged.off", @@ -186,9 +187,9 @@ public void validateDisbursement(JsonCommand command, boolean isAccountTransfer, if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) { BigDecimal totalCollateral = collectTotalCollateral(loanCollateralManagements); - // Validate the loan collateral value against the disbursedAmount - if (disbursedAmount.compareTo(totalCollateral) > 0) { - throw new LoanCollateralAmountNotSufficientException(disbursedAmount); + // Validate the loan collateral value against the total disbursed amount after this transaction + if (totalDisbursedAmount.compareTo(totalCollateral) > 0) { + throw new LoanCollateralAmountNotSufficientException(totalDisbursedAmount); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java index b8fe98fa562..0d6d0296aab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java @@ -208,7 +208,7 @@ public Money adjustDisburseAmount(final Loan loan, @NonNull final JsonCommand co .setPrincipal(loan.getLoanRepaymentScheduleDetail().getPrincipal().minus(diff).getAmount()); totalAmount = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount(); } - loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, disburseAmount.getAmount(), totalAmount); + loanDisbursementValidator.compareDisbursedToApprovedOrProposedPrincipal(loan, totalAmount); } return disburseAmount; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index e0b1b7b12d4..893d80aef76 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -39,6 +39,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; @@ -159,6 +160,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +@Slf4j @RequiredArgsConstructor @Transactional(readOnly = true) public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, LoanReadPlatformServiceCommon { @@ -540,14 +542,11 @@ public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId, fi @Override public LoanTransactionData retrieveLoanTransactionTemplate(final Long loanId) { - this.context.authenticatedUser(); - RepaymentTransactionTemplateMapper mapper = new RepaymentTransactionTemplateMapper(sqlGenerator); - String sql = "select " + mapper.schema(); - LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject(sql, mapper, // NOSONAR - LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.DOWN_PAYMENT.getValue(), - LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.DOWN_PAYMENT.getValue(), loanId, loanId); + final RepaymentTransactionTemplateMapper mapper = new RepaymentTransactionTemplateMapper(sqlGenerator); + LoanTransactionData loanTransactionData = this.jdbcTemplate.queryForObject("select " + mapper.schema(), mapper, // NOSONAR + LoanTransactionType.REPAYMENT.getValue(), LoanTransactionType.DOWN_PAYMENT.getValue(), loanId); final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); return LoanTransactionData.templateOnTop(loanTransactionData, paymentOptions); } @@ -2050,16 +2049,17 @@ private void updateInterestRatePeriodData(InterestRatePeriodData rate, LoanAccou @Override public Collection retrieveLoanIdsWithPendingIncomePostingTransactions() { LocalDate currentdate = DateUtils.getBusinessLocalDate(); - StringBuilder sqlBuilder = new StringBuilder().append(" select distinct loan.id from m_loan as loan ").append( - " inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = true) ") - .append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ") - .append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ") - .append(" left join m_loan_transaction as trans on (trans.is_reversed <> true and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) ") - .append(" where loan.loan_status_id = 300 ").append(" and loan.is_npa = false and loan.is_charged_off = false ") - .append(" and adddet.effective_date is not null ").append(" and trans.transaction_date is null ") - .append(" and adddet.effective_date < ? "); + final String sql = """ + select distinct loan.id from m_loan as loan + inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = true) + inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id + inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id + left join m_loan_transaction as trans on (trans.is_reversed <> true and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) + where loan.loan_status_id = 300 and loan.is_npa = false and loan.is_charged_off = false + and adddet.effective_date is not null and trans.transaction_date is null + and adddet.effective_date < ?\s"""; try { - return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { currentdate }); + return this.jdbcTemplate.queryForList(sql, Long.class, new Object[] { currentdate }); } catch (final EmptyResultDataAccessException e) { return null; } @@ -2125,33 +2125,20 @@ private static final class RepaymentTransactionTemplateMapper implements RowMapp public String schema() { // TODO: investigate whether it can be refactored to be more efficient - StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("(CASE "); - sqlBuilder.append( - "WHEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum in (?,?) AND tr.is_reversed = false) > ls.dueDate "); - sqlBuilder.append( - "THEN (select max(tr.transaction_date) as transaction_date from m_loan_transaction tr where tr.loan_id = l.id AND tr.transaction_type_enum in (?,?) AND tr.is_reversed = false) "); - sqlBuilder.append("ELSE ls.dueDate END) as transactionDate, "); - sqlBuilder.append( - "ls.principal_amount - coalesce(ls.principal_writtenoff_derived, 0) - coalesce(ls.principal_completed_derived, 0) as principalDue, "); - sqlBuilder.append( - "ls.interest_amount - coalesce(ls.interest_completed_derived, 0) - coalesce(ls.interest_waived_derived, 0) - coalesce(ls.interest_writtenoff_derived, 0) as interestDue, "); - sqlBuilder.append( - "ls.fee_charges_amount - coalesce(ls.fee_charges_completed_derived, 0) - coalesce(ls.fee_charges_writtenoff_derived, 0) - coalesce(ls.fee_charges_waived_derived, 0) as feeDue, "); - sqlBuilder.append( - "ls.penalty_charges_amount - coalesce(ls.penalty_charges_completed_derived, 0) - coalesce(ls.penalty_charges_writtenoff_derived, 0) - coalesce(ls.penalty_charges_waived_derived, 0) as penaltyDue, "); - sqlBuilder.append( - "l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, l.net_disbursal_amount as netDisbursalAmount, "); - sqlBuilder.append("rc." + sqlGenerator.escape("name") - + " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode "); - sqlBuilder.append("FROM m_loan l "); - sqlBuilder.append("JOIN m_currency rc on rc." + sqlGenerator.escape("code") + " = l.currency_code "); - sqlBuilder.append("JOIN m_loan_repayment_schedule ls ON ls.loan_id = l.id AND ls.completed_derived = false "); - sqlBuilder.append( - "JOIN((SELECT ls.loan_id, ls.duedate as datedue FROM m_loan_repayment_schedule ls WHERE ls.loan_id = ? and ls.completed_derived = false ORDER BY ls.duedate LIMIT 1)) asq on asq.loan_id = ls.loan_id "); - sqlBuilder.append("AND asq.datedue = ls.duedate "); - sqlBuilder.append("WHERE l.id = ? LIMIT 1"); - return sqlBuilder.toString(); + return " GREATEST(loan_transaction.transaction_date, ls.dueDate) as transactionDate," + + " coalesce(ls.principal_amount, 0) - coalesce(ls.principal_writtenoff_derived, 0) - coalesce(ls.principal_completed_derived, 0) as principalDue," + + " coalesce(ls.interest_amount, 0) - coalesce(ls.interest_completed_derived, 0) - coalesce(ls.interest_waived_derived, 0) - coalesce(ls.interest_writtenoff_derived, 0) as interestDue," + + " coalesce(ls.fee_charges_amount, 0) - coalesce(ls.fee_charges_completed_derived, 0) - coalesce(ls.fee_charges_writtenoff_derived, 0) - coalesce(ls.fee_charges_waived_derived, 0) as feeDue," + + " coalesce(ls.penalty_charges_amount, 0) - coalesce(ls.penalty_charges_completed_derived, 0) - coalesce(ls.penalty_charges_writtenoff_derived, 0) - coalesce(ls.penalty_charges_waived_derived, 0) as penaltyDue," + + " l.currency_code as currencyCode," + " l.currency_digits as currencyDigits," + + " l.currency_multiplesof as inMultiplesOf," + " l.net_disbursal_amount as netDisbursalAmount," + " rc." + + sqlGenerator.escape("name") + " as currencyName," + " rc.display_symbol as currencyDisplaySymbol," + + " rc.internationalized_name_code as currencyNameCode" + " FROM" + " m_loan l" + " JOIN m_currency rc on rc." + + sqlGenerator.escape("code") + " = l.currency_code" + " JOIN m_loan_repayment_schedule ls ON ls.loan_id = l.id" + + " LEFT JOIN (" + " select tr.loan_id, max(tr.transaction_date) as transaction_date" + " from m_loan_transaction tr" + + " where tr.transaction_type_enum in (?,?)" + " AND tr.is_reversed = false" + " group by tr.loan_id" + + " ) loan_transaction ON loan_transaction.loan_id = l.id" + " WHERE l.id = ?" + " ORDER BY ls.installment" + + " LIMIT 1"; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 066de64b964..c90c9e2a792 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -52,7 +52,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException; -import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.cob.service.AccountLockService; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -267,7 +267,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final PostDatedChecksRepository postDatedChecksRepository; private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository; private final LoanLifecycleStateMachine loanLifecycleStateMachine; - private final LoanAccountLockService loanAccountLockService; + private final AccountLockService loanAccountLockService; private final ExternalIdFactory externalIdFactory; private final LoanAccrualTransactionBusinessEventService loanAccrualTransactionBusinessEventService; private final ErrorHandler errorHandler; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index bd3f87c4384..1aa3e19ee1a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -228,7 +228,7 @@ public String retrieveAllLoanProducts(@Context final UriInfo uriInfo) { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Loan Product Details Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(operationId = "retrieveTemplateLoanProduct", summary = "Retrieve Loan Product Details Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "loanproducts/template") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanProductsApiResourceSwagger.GetLoanProductsTemplateResponse.class))) }) @@ -287,8 +287,8 @@ public String updateLoanProduct(@PathParam("productId") @Parameter(description = @Path("external-id/{externalProductId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Loan Product", description = "Retrieves a Loan Product\n\n" + "Example Requests:\n" + "\n" - + "loanproducts/external-id/2075e308-d4a8-44d9-8203-f5a947b8c2f4\n" + "\n" + "\n" + @Operation(operationId = "retrieveLoanProductDetailsByExternalId", summary = "Retrieve a Loan Product", description = "Retrieves a Loan Product\n\n" + + "Example Requests:\n" + "\n" + "loanproducts/external-id/2075e308-d4a8-44d9-8203-f5a947b8c2f4\n" + "\n" + "\n" + "loanproducts/external-id/2075e308-d4a8-44d9-8203-f5a947b8c2f4?template=true\n" + "\n" + "\n" + "loanproducts/external-id/2075e308-d4a8-44d9-8203-f5a947b8c2f4?fields=name,description,numberOfRepayments") @ApiResponses({ @@ -313,7 +313,7 @@ public String retrieveLoanProductDetails( @Path("external-id/{externalProductId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Loan Product", description = "Updates a Loan Product") + @Operation(operationId = "updateLoanProductByExternalId", summary = "Update a Loan Product", description = "Updates a Loan Product") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = LoanProductsApiResourceSwagger.PutLoanProductsProductIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanProductsApiResourceSwagger.PutLoanProductsProductIdResponse.class))) }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java index 72128a994d6..2638dd21686 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/DepositAccountOnHoldFundTransactionsApiResource.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.api; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -56,6 +57,7 @@ public class DepositAccountOnHoldFundTransactionsApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve deposit account on hold fund transactions", operationId = "retrieveAllDepositAccountOnHoldFundTransactions") public String retrieveAll(@PathParam("savingsId") final Long savingsId, @QueryParam("guarantorFundingId") final Long guarantorFundingId, @Context final UriInfo uriInfo, @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java index ef1e43358f9..a1ae79f17f3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountTransactionsApiResource.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.savings.api; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -78,6 +79,7 @@ private boolean is(final String commandParam, final String commandValue) { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Fixed Deposit Account Transaction Template", operationId = "retrieveTemplateFixedDepositAccountTransaction") public String retrieveTemplate(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId, // @QueryParam("command") final String commandParam, @Context final UriInfo uriInfo) { @@ -99,6 +101,7 @@ public String retrieveTemplate(@PathParam("fixedDepositAccountId") final Long fi @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a fixed deposit account transaction", operationId = "retrieveOneFixedDepositAccountTransaction") public String retrieveOne(@PathParam("fixedDepositAccountId") final Long fixedDepositAccountId, @PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java index 3c538c66fa7..22790ea8d8e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java @@ -111,7 +111,7 @@ public class FixedDepositAccountsApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Fixed Deposit Account Template", description = """ + @Operation(summary = "Retrieve Fixed Deposit Account Template", operationId = "retrieveTemplateFixedDepositAccount", description = """ This is a convenience resource. It can be useful when building maintenance user interface screens for fixed deposit applications. The template data returned consists of any or all of: @@ -142,7 +142,7 @@ public String template(@QueryParam("clientId") @Parameter(description = "clientI @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Fixed deposit applications/accounts", description = """ + @Operation(summary = "List Fixed deposit applications/accounts", operationId = "retrieveAllFixedDepositAccounts", description = """ Lists Fixed Deposit Accounts Example Requests: @@ -205,7 +205,7 @@ public String submitApplication(@Parameter(hidden = true) final String apiReques @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a fixed deposit application/account", description = """ + @Operation(summary = "Retrieve a fixed deposit application/account", operationId = "retrieveOneFixedDepositAccount", description = """ Retrieves a fixed deposit application/account Example Requests : @@ -341,7 +341,7 @@ private FixedDepositAccountData populateTemplateAndAssociations(final Long accou @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a fixed deposit application", description = "Fixed deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") + @Operation(summary = "Modify a fixed deposit application", operationId = "updateFixedDepositAccount", description = "Fixed deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PutFixedDepositAccountsAccountIdResponse.class))) public String update(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @@ -377,7 +377,7 @@ public String update(@PathParam("accountId") @Parameter(description = "accountId + "Calculates interest earned on a fixed deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + "Post Interest on Fixed Deposit Account:\n\n" + "Calculates and Posts interest earned on a fixed deposit account based on today's date and whether an interest posting or crediting event is due.\n\n" - + "Showing request/response for Calculate Interest on Fixed Deposit Account") + + "Showing request/response for Calculate Interest on Fixed Deposit Account", operationId = "handleCommandsFixedDepositAccount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.PostFixedDepositAccountsAccountIdResponse.class))) public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @@ -446,7 +446,7 @@ private boolean is(final String commandParam, final String commandValue) { @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a fixed deposit application", description = "At present we support hard delete of fixed deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the fixed deposit account.") + @Operation(summary = "Delete a fixed deposit application", operationId = "deleteFixedDepositAccount", description = "At present we support hard delete of fixed deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the fixed deposit account.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositAccountsApiResourceSwagger.DeleteFixedDepositAccountsAccountIdResponse.class))) public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java index 4cc92f66918..d34e5499571 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java @@ -114,7 +114,7 @@ public class FixedDepositProductsApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Fixed Deposit Product", description = """ + @Operation(summary = "Create a Fixed Deposit Product", operationId = "createFixedDepositProduct", description = """ Creates a Fixed Deposit Product Mandatory Fields: name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minDepositTerm, minDepositTermTypeId, accountingRule @@ -139,7 +139,7 @@ public String create(@Parameter(hidden = true) final String apiRequestBodyAsJson @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Fixed Deposit Product", description = "Updates a Fixed Deposit Product") + @Operation(summary = "Update a Fixed Deposit Product", operationId = "updateFixedDepositProduct", description = "Updates a Fixed Deposit Product") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = FixedDepositProductsApiResourceSwagger.PutFixedDepositProductsProductIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositProductsApiResourceSwagger.PutFixedDepositProductsProductIdResponse.class))) public String update(@PathParam("productId") @Parameter(description = "productId") final Long productId, @@ -157,7 +157,7 @@ public String update(@PathParam("productId") @Parameter(description = "productId @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Fixed Deposit Products", description = """ + @Operation(summary = "List Fixed Deposit Products", operationId = "retrieveAllFixedDepositProducts", description = """ Lists Fixed Deposit Products Example Requests: @@ -184,7 +184,7 @@ public String retrieveAll(@Context final UriInfo uriInfo) { @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Fixed Deposit Product", description = """ + @Operation(summary = "Retrieve a Fixed Deposit Product", operationId = "retrieveOneFixedDepositProduct", description = """ Retrieves a Fixed Deposit Product Example Requests: @@ -238,6 +238,7 @@ public String retrieveOne(@PathParam("productId") @Parameter(description = "prod @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Fixed Deposit Product Template", operationId = "retrieveTemplateFixedDepositProduct") public String retrieveTemplate(@Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME); @@ -338,7 +339,7 @@ private FixedDepositProductData handleTemplateRelatedData(final FixedDepositProd @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Fixed Deposit Product", description = "Deletes a Fixed Deposit Product") + @Operation(summary = "Delete a Fixed Deposit Product", operationId = "deleteFixedDepositProduct", description = "Deletes a Fixed Deposit Product") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FixedDepositProductsApiResourceSwagger.DeleteFixedDepositProductsProductIdResponse.class))) public String delete(@PathParam("productId") @Parameter(description = "productId") final Long productId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java index fa071e90b7d..52b0d7b03d9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountTransactionsApiResource.java @@ -87,7 +87,7 @@ private boolean is(final String commandParam, final String commandValue) { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Recurring Deposit Account Transaction Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Recurring Deposit Account Transaction Template", operationId = "retrieveTemplateRecurringDepositAccountTransaction", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n" + "Example Requests:\n" + "\n" + "recurringdepositaccounts/1/transactions/template?command=deposit\n" + "\n" + "recurringdepositaccounts/1/transactions/template?command=withdrawal") @@ -131,7 +131,7 @@ public String retrieveTemplate( @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Recurring Deposit Account Transaction", description = "Retrieves Recurring Deposit Account Transaction\n\n" + @Operation(summary = "Retrieve Recurring Deposit Account Transaction", operationId = "retrieveOneRecurringDepositAccountTransaction", description = "Retrieves Recurring Deposit Account Transaction\n\n" + "Example Requests:\n" + "\n" + "recurringdepositaccounts/1/transactions/1") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountTransactionsApiResourceSwagger.GetRecurringDepositAccountsRecurringDepositAccountIdTransactionsTransactionIdResponse.class))) }) @@ -155,7 +155,7 @@ public String retrieveOne( @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Deposit Transaction | Withdrawal Transaction", description = "Deposit Transaction:\n\n" + @Operation(summary = "Deposit Transaction | Withdrawal Transaction", operationId = "transactionRecurringDepositAccountTransaction", description = "Deposit Transaction:\n\n" + "Used for a deposit transaction\n\n" + "Withdrawal Transaction:\n\n" + "Used for a Withdrawal Transaction\n\n" + "Showing request/response for Deposit Transaction") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountTransactionsApiResourceSwagger.PostRecurringDepositAccountsRecurringDepositAccountIdTransactionsRequest.class))) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 25294dcb023..e2f5436d5c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -105,7 +105,7 @@ public class RecurringDepositAccountsApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve recurring Deposit Account Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for recurring deposit applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve recurring Deposit Account Template", operationId = "retrieveTemplateRecurringDepositAccount", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for recurring deposit applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n" + "recurringdepositaccounts/template?clientId=1\n" + "\n" + "\n" + "recurringdepositaccounts/template?clientId=1&productId=1") @ApiResponses({ @@ -129,7 +129,7 @@ public String template(@QueryParam("clientId") @Parameter(description = "clientI @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Recurring deposit applications/accounts", description = "Lists Recurring deposit applications/accounts\n\n" + @Operation(summary = "List Recurring deposit applications/accounts", operationId = "retrieveAllRecurringDepositAccounts", description = "Lists Recurring deposit applications/accounts\n\n" + "Example Requests:\n" + "\n" + "recurringdepositaccounts\n" + "\n" + "\n" + "recurringdepositaccounts?fields=name") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsResponse.class)))) }) @@ -163,7 +163,7 @@ public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("paged") @ @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new recurring deposit application", description = "Submits new recurring deposit application\n\n" + @Operation(summary = "Submit new recurring deposit application", operationId = "submitApplicationRecurringDepositAccount", description = "Submits new recurring deposit application\n\n" + "Mandatory Fields: clientId or groupId, productId, submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId\n\n" + "Optional Fields: accountNo, externalId, fieldOfficerId,linkAccountId(if provided initial deposit amount will be collected from this account),transferInterestToSavings(By enabling this flag all interest postings will be transferred to linked saving account )\n\n" + "Inherited from Product (if not provided): interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, lockinPeriodFrequency, lockinPeriodFrequencyType, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, charts, withHoldTax") @@ -184,7 +184,7 @@ public String submitApplication(@Parameter(hidden = true) final String apiReques @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a recurring deposit application/account", description = "Retrieves a recurring deposit application/account\n\n" + @Operation(summary = "Retrieve a recurring deposit application/account", operationId = "retrieveOneRecurringDepositAccount", description = "Retrieves a recurring deposit application/account\n\n" + "Example Requests :\n" + "\n" + "recurringdepositaccounts/1\n" + "\n" + "\n" + "recurringdepositaccounts/1?associations=all") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.GetRecurringDepositAccountsAccountIdResponse.class))) }) @@ -261,7 +261,7 @@ private RecurringDepositAccountData populateTemplateAndAssociations(final Long a @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a recurring deposit application", description = "Recurring deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") + @Operation(summary = "Modify a recurring deposit application", operationId = "updateRecurringDepositAccount", description = "Recurring deposit application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PutRecurringDepositAccountsAccountIdResponse.class))) }) @@ -303,7 +303,7 @@ public String update(@PathParam("accountId") @Parameter(description = "accountId + "Calculates interest earned on a recurring deposit account based on todays date. It does not attempt to post or credit the interest on the account. That is responsibility of the Post Interest API that will likely be called by overnight process.\n\n" + "Post Interest on recurring Deposit Account:\n\n" + "Calculates and Posts interest earned on a recurring deposit account based on todays date and whether an interest posting or crediting event is due.\n\n" - + "Showing request/response for 'Post Interest on recurring Deposit Account'") + + "Showing request/response for 'Post Interest on recurring Deposit Account'", operationId = "handleCommandsRecurringDepositAccount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.PostRecurringDepositAccountsAccountIdResponse.class))) }) @@ -377,7 +377,7 @@ private boolean is(final String commandParam, final String commandValue) { @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a recurring deposit application", description = "At present we support hard delete of recurring deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the recurring deposit account.") + @Operation(summary = "Delete a recurring deposit application", operationId = "deleteRecurringDepositAccount", description = "At present we support hard delete of recurring deposit application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the recurring deposit account.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositAccountsApiResourceSwagger.DeleteRecurringDepositAccountsResponse.class))) }) public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { @@ -393,6 +393,7 @@ public String delete(@PathParam("accountId") @Parameter(description = "accountId @Path("{accountId}/template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve recurring deposit account closure template", operationId = "accountClosureTemplateRecurringDepositAccount") public String accountClosureTemplate(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Context final UriInfo uriInfo) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java index 9b9e23f7b05..fa3c6d66bdd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java @@ -114,10 +114,10 @@ public class RecurringDepositProductsApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Recurring Deposit Product", description = "Creates a Recurring Deposit Product\n\n" - + "Mandatory Fields: name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minDepositTerm, minDepositTermTypeId, accountingRule, depositAmount\n\n" + @Operation(summary = "Create a Recurring Deposit Product", operationId = "createRecurringDepositProduct", description = "Creates a Recurring Deposit Product\n\n" + + "Mandatory Fields: name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minDepositTerm, minDepositTermTypeId, accountingRule, depositAmount, charts\n\n" + "Mandatory Fields for Cash based accounting (accountingRule = 2): savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId\n\n" - + "Optional Fields: lockinPeriodFrequency, lockinPeriodFrequencyType, maxDepositTerm, maxDepositTermTypeId, inMultiplesOfDepositTerm, inMultiplesOfDepositTermTypeId, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, charts, minDepositAmount, maxDepositAmount, withHoldTax, taxGroupId") + + "Optional Fields: lockinPeriodFrequency, lockinPeriodFrequencyType, maxDepositTerm, maxDepositTermTypeId, inMultiplesOfDepositTerm, inMultiplesOfDepositTermTypeId, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnTypeId, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, minDepositAmount, maxDepositAmount, withHoldTax, taxGroupId") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.PostRecurringDepositProductsRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.PostRecurringDepositProductsResponse.class))) }) @@ -135,7 +135,7 @@ public String create(@Parameter(hidden = true) final String apiRequestBodyAsJson @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Recurring Deposit Product", description = "Updates a Recurring Deposit Product") + @Operation(summary = "Update a Recurring Deposit Product", operationId = "updateRecurringDepositProduct", description = "Updates a Recurring Deposit Product") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.PutRecurringDepositProductsRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.PutRecurringDepositProductsResponse.class))) }) @@ -154,8 +154,8 @@ public String update(@PathParam("productId") @Parameter(description = "productId @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Recuring Deposit Products", description = "Lists Recuring Deposit Products\n\n" + "Example Requests:\n" - + "\n" + "recurringdepositproducts\n" + "\n" + "\n" + "recurringdepositproducts?fields=name") + @Operation(summary = "List Recuring Deposit Products", operationId = "retrieveAllRecurringDepositProducts", description = "Lists Recuring Deposit Products\n\n" + + "Example Requests:\n" + "\n" + "recurringdepositproducts\n" + "\n" + "\n" + "recurringdepositproducts?fields=name") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.GetRecurringDepositProductsResponse.class)))) }) public String retrieveAll(@Context final UriInfo uriInfo) { @@ -175,7 +175,7 @@ public String retrieveAll(@Context final UriInfo uriInfo) { @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Recurring Deposit Product", description = "Retrieves a Recurring Deposit Product\n\n" + @Operation(summary = "Retrieve a Recurring Deposit Product", operationId = "retrieveOneRecurringDepositProduct", description = "Retrieves a Recurring Deposit Product\n\n" + "Example Requests:\n" + "\n" + "recurringdepositproducts/1\n" + "\n" + "\n" + "recurringdepositproducts/1?template=true\n" + "\n" + "\n" + "recurringdepositproducts/1?fields=name,description") @ApiResponses({ @@ -221,6 +221,7 @@ public String retrieveOne(@PathParam("productId") @Parameter(description = "prod @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Recurring Deposit Product Template", operationId = "retrieveTemplateRecurringDepositProduct") public String retrieveTemplate(@Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.RECURRING_DEPOSIT_PRODUCT_RESOURCE_NAME); @@ -317,7 +318,7 @@ private RecurringDepositProductData handleTemplateRelatedData(final RecurringDep @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Recurring Deposit Product", description = "Deletes a Recurring Deposit Product") + @Operation(summary = "Delete a Recurring Deposit Product", operationId = "deleteRecurringDepositProduct", description = "Deletes a Recurring Deposit Product") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = RecurringDepositProductsApiResourceSwagger.DeleteRecurringDepositProductsProductIdResponse.class))) }) public String delete(@PathParam("productId") @Parameter(description = "productId") final Long productId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java index 40cdaa71562..2e77930d549 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountChargesApiResource.java @@ -114,7 +114,7 @@ public String retrieveAllSavingsAccountCharges( @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Savings Charges Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Savings Charges Template", operationId = "retrieveTemplateSavingsAccountCharge", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "savingsaccounts/1/charges/template") @ApiResponses({ diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index b2aff4081d1..e8409ca27a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -81,14 +81,11 @@ public class SavingsAccountTransactionsApiResource { private final PaymentTypeReadService paymentTypeReadPlatformService; private final SavingsAccountTransactionSearchService transactionsSearchService; - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - @GET @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a savings account transaction template", operationId = "retrieveTemplateSavingsAccountTransaction") public String retrieveTemplate(@PathParam("savingsId") final Long savingsId, // @QueryParam("command") final String commandParam, @Context final UriInfo uriInfo) { @@ -111,6 +108,7 @@ public String retrieveTemplate(@PathParam("savingsId") final Long savingsId, @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a savings account transaction", operationId = "retrieveOneSavingsAccountTransaction") public String retrieveOne(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) { @@ -162,7 +160,7 @@ public String searchTransactions(@PathParam("savingsId") @Parameter(description @Path("query") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Advanced search Savings Account Transactions") + @Operation(summary = "Advanced search Savings Account Transactions", operationId = "advancedQuerySavingsAccountTransactions") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = List.class))) public String advancedQuery(@PathParam("savingsId") @Parameter(description = "savingsId") final Long savingsId, PagedLocalRequest queryRequest, @Context final UriInfo uriInfo) { @@ -173,36 +171,25 @@ public String advancedQuery(@PathParam("savingsId") @Parameter(description = "sa @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Create a savings account transaction", operationId = "createSavingsAccountTransaction") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountTransactionsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountTransactionsResponse.class))) public String transaction(@PathParam("savingsId") final Long savingsId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson); - CommandProcessingResult result = null; - if (is(commandParam, "deposit")) { - final CommandWrapper commandRequest = builder.savingsAccountDeposit(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "gsimDeposit")) { - final CommandWrapper commandRequest = builder.gsimSavingsAccountDeposit(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawal")) { - final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterestAsOn")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_HOLD_AMOUNT)) { - final CommandWrapper commandRequest = builder.holdAmount(savingsId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - - if (result == null) { - // - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "deposit", "withdrawal", SavingsApiConstants.COMMAND_HOLD_AMOUNT }); - } - + final CommandWrapper commandRequest = switch (StringUtils.trimToEmpty(commandParam)) { + case "deposit" -> builder.savingsAccountDeposit(savingsId).build(); + case "gsimDeposit" -> builder.gsimSavingsAccountDeposit(savingsId).build(); + case "withdrawal" -> builder.savingsAccountWithdrawal(savingsId).build(); + case "force-withdrawal" -> builder.savingsAccountForceWithdrawal(savingsId).build(); + case "postInterestAsOn" -> builder.savingsAccountInterestPosting(savingsId).build(); + case SavingsApiConstants.COMMAND_HOLD_AMOUNT -> builder.holdAmount(savingsId).build(); + default -> throw new UnrecognizedQueryParamException("command", commandParam, "deposit", "withdrawal", "force-withdrawal", + SavingsApiConstants.COMMAND_HOLD_AMOUNT); + }; + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } @@ -210,7 +197,7 @@ public String transaction(@PathParam("savingsId") final Long savingsId, @QueryPa @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Undo/Reverse/Modify/Release Amount transaction API", description = "Undo/Reverse/Modify/Release Amount transaction API\n\n" + @Operation(summary = "Undo/Reverse/Modify/Release Amount transaction API", operationId = "adjustSavingsAccountTransaction", description = "Undo/Reverse/Modify/Release Amount transaction API\n\n" + "Example Requests:\n" + "\n" + "\n" + "savingsaccounts/{savingsId}/transactions/{transactionId}?command=reverse\n" + "\n" + "Accepted command = undo, reverse, modify, releaseAmount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountBulkReversalTransactionsRequest.class))) @@ -225,28 +212,19 @@ public String adjustTransaction(@PathParam("savingsId") final Long savingsId, @P final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - CommandProcessingResult result = null; - if (is(commandParam, SavingsApiConstants.COMMAND_UNDO_TRANSACTION)) { - final CommandWrapper commandRequest = builder.undoSavingsAccountTransaction(savingsId, transactionId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_REVERSE_TRANSACTION)) { - final CommandWrapper commandRequest = builder.reverseSavingsAccountTransaction(savingsId, transactionId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_ADJUST_TRANSACTION)) { - final CommandWrapper commandRequest = builder.adjustSavingsAccountTransaction(savingsId, transactionId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_RELEASE_AMOUNT)) { - final CommandWrapper commandRequest = builder.releaseAmount(savingsId, transactionId).build(); - result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - - if (result == null) { - // - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { SavingsApiConstants.COMMAND_UNDO_TRANSACTION, SavingsApiConstants.COMMAND_ADJUST_TRANSACTION, - SavingsApiConstants.COMMAND_RELEASE_AMOUNT, SavingsApiConstants.COMMAND_REVERSE_TRANSACTION }); - } - + final CommandWrapper commandRequest = switch (StringUtils.trimToEmpty(commandParam)) { + case SavingsApiConstants.COMMAND_UNDO_TRANSACTION -> builder.undoSavingsAccountTransaction(savingsId, transactionId).build(); + case SavingsApiConstants.COMMAND_REVERSE_TRANSACTION -> + builder.reverseSavingsAccountTransaction(savingsId, transactionId).build(); + case SavingsApiConstants.COMMAND_ADJUST_TRANSACTION -> + builder.adjustSavingsAccountTransaction(savingsId, transactionId).build(); + case SavingsApiConstants.COMMAND_RELEASE_AMOUNT -> builder.releaseAmount(savingsId, transactionId).build(); + default -> throw new UnrecognizedQueryParamException("command", commandParam, SavingsApiConstants.COMMAND_UNDO_TRANSACTION, + SavingsApiConstants.COMMAND_ADJUST_TRANSACTION, SavingsApiConstants.COMMAND_RELEASE_AMOUNT, + SavingsApiConstants.COMMAND_REVERSE_TRANSACTION); + }; + + final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); return this.toApiJsonSerializer.serialize(result); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java index 2630c535199..1548bdfdd53 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountsApiResource.java @@ -76,11 +76,10 @@ import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; @Path("/v1/savingsaccounts") @Component -@Tag(name = "Savings Account", description = "Savings accounts are instances of a particular savings product created for an individual or group. An application process around the creation of accounts is also supported.") +@Tag(name = "Savings Account", description = "Savings accounts are instances of a particular savings product created for an individual or group.") @RequiredArgsConstructor public class SavingsAccountsApiResource { @@ -99,7 +98,7 @@ public class SavingsAccountsApiResource { @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Savings Account Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Savings Account Template", operationId = "retrieveSavingsAccountTemplate", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed Value Lists\n\n" + "Example Requests:\n" + "\n" + "savingsaccounts/template?clientId=1\n" + "\n" + "\n" + "savingsaccounts/template?clientId=1&productId=1") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsTemplateResponse.class))) @@ -108,12 +107,9 @@ public String template(@QueryParam("clientId") @Parameter(description = "clientI @QueryParam("productId") @Parameter(description = "productId") final Long productId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") @Parameter(description = "staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @Context final UriInfo uriInfo) { - context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - final SavingsAccountData savingsAccount = savingsAccountTemplateReadPlatformService.retrieveTemplate(clientId, groupId, productId, staffInSelectedOfficeOnly); - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, savingsAccount, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); } @@ -121,7 +117,7 @@ public String template(@QueryParam("clientId") @Parameter(description = "clientI @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List savings applications/accounts", description = "Lists savings applications/accounts\n\n" + @Operation(summary = "List savings applications/accounts", operationId = "retrieveAllSavingsAccounts", description = "Lists savings applications/accounts\n\n" + "Example Requests:\n" + "\n" + "savingsaccounts\n" + "\n" + "\n" + "savingsaccounts?fields=name") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.GetSavingsAccountsResponse.class))) public String retrieveAll(@Context final UriInfo uriInfo, @@ -133,15 +129,12 @@ public String retrieveAll(@Context final UriInfo uriInfo, @QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder) { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - sqlValidator.validate(orderBy); sqlValidator.validate(sortOrder); sqlValidator.validate(externalId); final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset) .orderBy(orderBy).sortOrder(sortOrder).build(); - final Page products = savingsAccountReadPlatformService.retrieveAll(searchParameters); - final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return toApiJsonSerializer.serialize(settings, products, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS); } @@ -149,7 +142,7 @@ public String retrieveAll(@Context final UriInfo uriInfo, @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Submit new savings application", description = "Submits new savings application\n\n" + @Operation(summary = "Submit new savings application", operationId = "submitSavingsApplication", description = "Submits new savings application\n\n" + "Mandatory Fields: clientId or groupId, productId, submittedOnDate\n\n" + "Optional Fields: accountNo, externalId, fieldOfficerId\n\n" + "Inherited from Product (if not provided): nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, allowOverdraft, overdraftLimit, withHoldTax\n\n" @@ -157,24 +150,8 @@ public String retrieveAll(@Context final UriInfo uriInfo, @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsResponse.class))) public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().createSavingsAccount().withJson(apiRequestBodyAsJson).build(); - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("/gsim") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String submitGSIMApplication(final String apiRequestBodyAsJson) { - - final CommandWrapper commandRequest = new CommandWrapperBuilder().createGSIMAccount().withJson(apiRequestBodyAsJson).build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); } @@ -182,11 +159,11 @@ public String submitGSIMApplication(final String apiRequestBodyAsJson) { @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a savings account", operationId = "retrieveSavingsAccount") public SavingsAccountData retrieveOne(@PathParam("accountId") final Long accountId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { - return retrieveSavingAccount(accountId, null, staffInSelectedOfficeOnly, chargeStatus, uriInfo); } @@ -194,11 +171,11 @@ public SavingsAccountData retrieveOne(@PathParam("accountId") final Long account @Path("/external-id/{externalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a savings account by external ID", operationId = "retrieveSavingsAccountByExternalId") public SavingsAccountData retrieveOne(@PathParam("externalId") final String externalId, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, @DefaultValue("all") @QueryParam("chargeStatus") final String chargeStatus, @QueryParam("associations") final String associations, @Context final UriInfo uriInfo) { - return retrieveSavingAccount(null, externalId, staffInSelectedOfficeOnly, chargeStatus, uriInfo); } @@ -206,7 +183,7 @@ public SavingsAccountData retrieveOne(@PathParam("externalId") final String exte @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" + @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", operationId = "updateSavingsAccount", description = "Modify a savings application:\n\n" + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" + "Modify savings account withhold tax applicability:\n\n" + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" @@ -224,7 +201,7 @@ public String update(@PathParam("accountId") @Parameter(description = "accountId @Path("/external-id/{externalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability", description = "Modify a savings application:\n\n" + @Operation(summary = "Modify a savings application | Modify savings account withhold tax applicability by externalId", operationId = "updateSavingsAccountByExternalId", description = "Modify a savings application by externalId:\n\n" + "Savings application can only be modified when in 'Submitted and pending approval' state. Once the application is approved, the details cannot be changed using this method. Specific api endpoints will be created to allow change of interest detail such as rate, compounding period, posting period etc\n\n" + "Modify savings account withhold tax applicability:\n\n" + "Savings application's withhold tax can be modified when in 'Active' state. Once the application is activated, can modify the account withhold tax to post tax or vice-versa" @@ -238,74 +215,6 @@ public String update(@PathParam("externalId") @Parameter(description = "external return updateSavingAccount(null, externalId, apiRequestBodyAsJson, commandParam); } - @PUT - @Path("/gsim/{parentAccountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String updateGsim(@PathParam("parentAccountId") final Long parentAccountId, final String apiRequestBodyAsJson) { - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateGSIMAccount(parentAccountId).withJson(apiRequestBodyAsJson) - .build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - - return toApiJsonSerializer.serialize(result); - } - - @POST - @Path("/gsimcommands/{parentAccountId}") - @Consumes({ MediaType.APPLICATION_JSON }) - @Produces({ MediaType.APPLICATION_JSON }) - public String handleGSIMCommands(@PathParam("parentAccountId") final Long parentAccountId, - @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { - - String jsonApiRequest = apiRequestBodyAsJson; - if (StringUtils.isBlank(jsonApiRequest)) { - jsonApiRequest = "{}"; - } - - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - - CommandProcessingResult result = null; - if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectGSIMAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveGSIMAccountApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoGSIMApplicationApproval(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.gsimAccountActivation(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "applyAnnualFees")) { - final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeGSIMApplication(parentAccountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } - if (result == null) { - throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", - SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, - SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, - SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); - } - - return toApiJsonSerializer.serialize(result); - } - @POST @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @@ -341,13 +250,12 @@ public String handleGSIMCommands(@PathParam("parentAccountId") final Long parent + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" + "Unblock Savings Account debit transactions:\n\n" + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" - + "Showing request/response for 'Unassign Savings Officer'") + + "Showing request/response for 'Unassign Savings Officer'", operationId = "handleCommandsSavingsAccount") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) public String handleCommands(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Parameter(hidden = true) final String apiRequestBodyAsJson) { - return handleCommands(accountId, null, commandParam, apiRequestBodyAsJson); } @@ -386,25 +294,20 @@ public String handleCommands(@PathParam("accountId") @Parameter(description = "a + "Block Savings Account Debit transactions:\n\n" + "All types of debit operations from Savings account wil be blocked\n\n" + "Unblock Savings Account debit transactions:\n\n" + "It unblocks the Saving account's debit operations. Now all types of debits can be transacted from Savings account\n\n" - + "Showing request/response for 'Unassign Savings Officer'") + + "Showing request/response for 'Unassign Savings Officer'", operationId = "handleCommandsSavingsAccountByExternalId") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.PostSavingsAccountsAccountIdResponse.class))) public String handleCommands(@PathParam("externalId") @Parameter(description = "externalId") final String externalId, @QueryParam("command") @Parameter(description = "command") final String commandParam, @Parameter(hidden = true) final String apiRequestBodyAsJson) { - return handleCommands(null, externalId, commandParam, apiRequestBodyAsJson); } - private boolean is(final String commandParam, final String commandValue) { - return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); - } - @DELETE @Path("{accountId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") + @Operation(summary = "Delete a savings application", operationId = "deleteSavingsAccount", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) public String delete(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId) { @@ -415,7 +318,7 @@ public String delete(@PathParam("accountId") @Parameter(description = "accountId @Path("/external-id/{externalId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a savings application", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") + @Operation(summary = "Delete a savings application by externalId", operationId = "deleteSavingsAccountByExternalId", description = "At present we support hard delete of savings application so long as its in 'Submitted and pending approval' state. One the application is moves past this state, it is not possible to do a 'hard' delete of the application or the account. An API endpoint will be added to close/de-activate the savings account.") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsAccountsApiResourceSwagger.DeleteSavingsAccountsAccountIdResponse.class))) public String delete(@PathParam("externalId") @Parameter(description = "externalId") final String externalId) { @@ -467,125 +370,62 @@ public String postSavingsTransactionTemplate(@FormDataParam("file") InputStream private SavingsAccountData retrieveSavingAccount(Long accountId, String externalId, boolean staffInSelectedOfficeOnly, String chargeStatus, UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); - if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) { throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" }); } - ExternalId accountExternalId = ExternalIdFactory.produce(externalId); accountId = getResolvedAccountId(accountId, accountExternalId); final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId); - return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo); } private String updateSavingAccount(Long accountId, String externalId, String apiRequestBodyAsJson, String commandParam) { ExternalId accountExternalId = ExternalIdFactory.produce(externalId); accountId = getResolvedAccountId(accountId, accountExternalId); - if (is(commandParam, "updateWithHoldTax")) { final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson).updateWithHoldTax(accountId) .build(); final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); return toApiJsonSerializer.serialize(result); } - final CommandWrapper commandRequest = new CommandWrapperBuilder().updateSavingsAccount(accountId).withJson(apiRequestBodyAsJson) .build(); - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); } private String handleCommands(Long accountId, String externalId, String commandParam, String apiRequestBodyAsJson) { ExternalId accountExternalId = ExternalIdFactory.produce(externalId); accountId = getResolvedAccountId(accountId, accountExternalId); - String jsonApiRequest = apiRequestBodyAsJson; if (StringUtils.isBlank(jsonApiRequest)) { jsonApiRequest = "{}"; } - final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest); - CommandProcessingResult result = null; if (is(commandParam, "reject")) { - final CommandWrapper commandRequest = builder.rejectSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "withdrawnByApplicant")) { - final CommandWrapper commandRequest = builder.withdrawSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + result = commandsSourceWritePlatformService.logCommandSource(builder.rejectSavingsAccountApplication(accountId).build()); } else if (is(commandParam, "approve")) { - final CommandWrapper commandRequest = builder.approveSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + result = commandsSourceWritePlatformService.logCommandSource(builder.approveSavingsAccountApplication(accountId).build()); } else if (is(commandParam, "undoapproval")) { - final CommandWrapper commandRequest = builder.undoSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + result = commandsSourceWritePlatformService.logCommandSource(builder.undoSavingsAccountApplication(accountId).build()); } else if (is(commandParam, "activate")) { - final CommandWrapper commandRequest = builder.savingsAccountActivation(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "calculateInterest")) { - final CommandWrapper commandRequest = builder.withNoJsonBody().savingsAccountInterestCalculation(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "postInterest")) { - final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "applyAnnualFees")) { - final CommandWrapper commandRequest = builder.savingsAccountApplyAnnualFees(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + result = commandsSourceWritePlatformService.logCommandSource(builder.savingsAccountActivation(accountId).build()); } else if (is(commandParam, "close")) { - final CommandWrapper commandRequest = builder.closeSavingsAccountApplication(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, "assignSavingsOfficer")) { - final CommandWrapper commandRequest = builder.assignSavingsOfficer(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); - } else if (is(commandParam, "unassignSavingsOfficer")) { - final CommandWrapper commandRequest = builder.unassignSavingsOfficer(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - return toApiJsonSerializer.serialize(result); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) { - final CommandWrapper commandRequest = builder.blockDebitsFromSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) { - final CommandWrapper commandRequest = builder.unblockDebitsFromSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) { - final CommandWrapper commandRequest = builder.blockCreditsToSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) { - final CommandWrapper commandRequest = builder.unblockCreditsToSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.blockSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - } else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) { - final CommandWrapper commandRequest = builder.unblockSavingsAccount(accountId).build(); - result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + result = commandsSourceWritePlatformService.logCommandSource(builder.closeSavingsAccountApplication(accountId).build()); } - if (result == null) { - // throw new UnrecognizedQueryParamException("command", commandParam, - new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest", - "postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer", - SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT, - SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT, - SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT }); + new Object[] { "reject", "approve", "undoapproval", "activate", "close" }); } - return toApiJsonSerializer.serialize(result); } private String deleteSavingAccount(Long accountId, String externalId) { ExternalId accountExternalId = ExternalIdFactory.produce(externalId); accountId = getResolvedAccountId(accountId, accountExternalId); - - final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteSavingsAccount(accountId).build(); - - final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); - + final CommandProcessingResult result = commandsSourceWritePlatformService + .logCommandSource(new CommandWrapperBuilder().deleteSavingsAccount(accountId).build()); return toApiJsonSerializer.serialize(result); } @@ -601,43 +441,32 @@ private Long getResolvedAccountId(Long accountId, ExternalId accountExternalId) return resolvedAccountId; } + private boolean is(final String commandParam, final String commandValue) { + return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); + } + private SavingsAccountData populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount, final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) { - Collection transactions = null; Collection charges = null; - final Set associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters()); if (!associationParameters.isEmpty()) { - if (associationParameters.contains("all")) { - associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges)); + associationParameters.addAll(Arrays.asList("transactions", "charges")); } - - if (associationParameters.contains(SavingsApiConstants.transactions)) { - final Collection currentTransactions = savingsAccountReadPlatformService - .retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT); - if (!CollectionUtils.isEmpty(currentTransactions)) { - transactions = currentTransactions; - } + if (associationParameters.contains("transactions")) { + transactions = savingsAccountReadPlatformService.retrieveAllTransactions(accountId, DepositAccountType.SAVINGS_DEPOSIT); } - - if (associationParameters.contains(SavingsApiConstants.charges)) { - final Collection currentCharges = savingsAccountChargeReadPlatformService - .retrieveSavingsAccountCharges(accountId, chargeStatus); - if (!CollectionUtils.isEmpty(currentCharges)) { - charges = currentCharges; - } + if (associationParameters.contains("charges")) { + charges = savingsAccountChargeReadPlatformService.retrieveSavingsAccountCharges(accountId, chargeStatus); } } - SavingsAccountData templateData = null; final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); if (settings.isTemplate()) { templateData = savingsAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(), savingsAccount.getGroupId(), savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly); } - return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java index 63800300f27..69c042c4b46 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java @@ -103,7 +103,7 @@ public class SavingsProductsApiResource { @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Create a Savings Product", description = "Creates a Savings Product\n\n" + @Operation(summary = "Create a Savings Product", operationId = "createSavingsProduct", description = "Creates a Savings Product\n\n" + "Mandatory Fields: name, shortName, description, currencyCode, digitsAfterDecimal,inMultiplesOf, nominalAnnualInterestRate, interestCompoundingPeriodType, interestCalculationType, interestCalculationDaysInYearType,accountingRule\n\n" + "Mandatory Fields for Cash based accounting (accountingRule = 2): savingsReferenceAccountId, savingsControlAccountId, interestOnSavingsAccountId, incomeFromFeeAccountId, transfersInSuspenseAccountId, incomeFromPenaltyAccountId\n\n" + "Optional Fields: minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeForTransfers, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, charges, allowOverdraft, overdraftLimit, minBalanceForInterestCalculation,withHoldTax,taxGroupId,accountMapping, lienAllowed, maxAllowedLienLimit") @@ -122,7 +122,7 @@ public String create(@Parameter(hidden = true) final String apiRequestBodyAsJson @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Update a Savings Product", description = "Updates a Savings Product") + @Operation(summary = "Update a Savings Product", operationId = "updateSavingsProduct", description = "Updates a Savings Product") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.PutSavingsProductsProductIdRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.PutSavingsProductsProductIdResponse.class))) public String update(@PathParam("productId") @Parameter(description = "productId") final Long productId, @@ -140,8 +140,8 @@ public String update(@PathParam("productId") @Parameter(description = "productId @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Savings Products", description = "Lists Savings Products\n\n" + "Example Requests:\n" + "\n" - + "savingsproducts\n" + "\n" + "savingsproducts?fields=name") + @Operation(summary = "List Savings Products", operationId = "retrieveAllSavingsProducts", description = "Lists Savings Products\n\n" + + "Example Requests:\n" + "\n" + "savingsproducts\n" + "\n" + "savingsproducts?fields=name") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.GetSavingsProductsResponse.class)))) public String retrieveAll(@Context final UriInfo uriInfo) { @@ -157,8 +157,9 @@ public String retrieveAll(@Context final UriInfo uriInfo) { @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Savings Product", description = "Retrieves a Savings Product\n\n" + "Example Requests:\n" + "\n" - + "savingsproducts/1\n" + "\n" + "savingsproducts/1?template=true\n" + "\n" + "savingsproducts/1?fields=name,description") + @Operation(summary = "Retrieve a Savings Product", operationId = "retrieveOneSavingsProduct", description = "Retrieves a Savings Product\n\n" + + "Example Requests:\n" + "\n" + "savingsproducts/1\n" + "\n" + "savingsproducts/1?template=true\n" + "\n" + + "savingsproducts/1?fields=name,description") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.GetSavingsProductsProductIdResponse.class))) public String retrieveOne(@PathParam("productId") @Parameter(description = "productId") final Long productId, @Context final UriInfo uriInfo) { @@ -198,7 +199,7 @@ public String retrieveOne(@PathParam("productId") @Parameter(description = "prod @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve Savings Product Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve Savings Product Template", operationId = "retrieveTemplateSavingsProduct", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "Account Mapping:\n" + "\n" + "savingsproducts/template") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.GetSavingsProductsTemplateResponse.class))) @@ -290,7 +291,7 @@ private SavingsProductData handleTemplateRelatedData(final SavingsProductData sa @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Delete a Savings Product", description = "Deletes a Savings Product") + @Operation(summary = "Delete a Savings Product", operationId = "deleteSavingsProduct", description = "Deletes a Savings Product") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SavingsProductsApiResourceSwagger.DeleteSavingsProductsProductIdResponse.class))) public String delete(@PathParam("productId") @Parameter(description = "productId") final Long productId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java index e3a7401092d..33069eae69f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java @@ -366,6 +366,10 @@ private void validateChartsData(JsonElement element, DataValidatorBuilder baseDa final JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(chartsParamName, element); baseDataValidator.reset().parameter(chartsParamName).value(array).notNull().jsonArrayNotEmpty(); + if (array == null) { + return; + } + for (int i = 0; i < array.size(); i++) { final JsonObject interestRateChartElement = array.get(i).getAsJsonObject(); final String json = this.fromApiJsonHelper.toJson(interestRateChartElement); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java index 3342eeec4d8..e1ea9fd2b55 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java @@ -65,6 +65,7 @@ import java.util.Locale; import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; @@ -120,6 +121,7 @@ public class DepositAccountAssembler { private final PaymentDetailAssembler paymentDetailAssembler; private final ExternalIdFactory externalIdFactory; + private final ConfigurationDomainService configurationDomainService; @Autowired public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, @@ -130,7 +132,8 @@ public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper sav final DepositProductAssembler depositProductAssembler, final RecurringDepositProductRepository recurringDepositProductRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context, - final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory) { + final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory, + final ConfigurationDomainService configurationDomainService) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; @@ -146,6 +149,7 @@ public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper sav this.context = context; this.paymentDetailAssembler = paymentDetailAssembler; this.externalIdFactory = externalIdFactory; + this.configurationDomainService = configurationDomainService; } /** @@ -356,7 +360,7 @@ public SavingsAccount assembleFrom(final JsonCommand command, final AppUser subm } if (account != null) { - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); account.validateNewApplicationState(depositAccountType.resourceName()); } @@ -365,12 +369,12 @@ public SavingsAccount assembleFrom(final JsonCommand command, final AppUser subm public SavingsAccount assembleFrom(final Long savingsId, DepositAccountType depositAccountType) { final SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId, depositAccountType); - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); return account; } public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) { - savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); } public DepositAccountTermAndPreClosure assembleAccountTermAndPreClosure(final JsonCommand command, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 8421e9ca07c..40c0e94acbf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -312,10 +312,7 @@ private List calculateInterestPayable(final MathContext mc, final List savingsAccountTransactionDetailsForPostingPeriodList = toSavingsAccountTransactionDetailsForPostingPeriodList( transactions); for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - boolean isUserPosting = false; - if (postedAsOnTransactionDates.contains(periodInterval.endDate())) { - isUserPosting = true; - } + boolean isUserPosting = postedAsOnTransactionDates.contains(periodInterval.endDate()); final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, savingsAccountTransactionDetailsForPostingPeriodList, this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index 0fd16e6dbba..2574ba5fa9f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -330,7 +330,7 @@ public SavingsAccount assembleFrom(final JsonCommand command, final AppUser subm minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, iswithdrawalFeeApplicableForTransfer, charges, allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, maxAllowedLienLimit, lienAllowed, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax); - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -381,7 +381,7 @@ public SavingsAccount loadTransactionsToSavingsAccount(final SavingsAccount acco } } - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); return account; } @@ -421,7 +421,7 @@ public boolean isRelaxingDaysConfigForPivotDateEnabled() { } public void setHelpers(final SavingsAccount account) { - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); } /** @@ -465,7 +465,7 @@ public SavingsAccount assembleFrom(final Client client, final Group group, final product.isMinRequiredBalanceEnforced(), product.minRequiredBalance(), product.maxAllowedLienLimit(), product.isLienAllowed(), product.nominalAnnualInterestRateOverdraft(), product.minOverdraftForInterestCalculation(), product.withHoldTax()); - account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); account.validateNewApplicationState(SAVINGS_ACCOUNT_RESOURCE_NAME); @@ -475,7 +475,7 @@ public SavingsAccount assembleFrom(final Client client, final Group group, final } public void assignSavingAccountHelpers(final SavingsAccount savingsAccount) { - savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); + savingsAccount.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper, this.configurationDomainService); } public void assignSavingAccountHelpers(final SavingsAccountData savingsAccountData) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index fb16621dc64..efc877587eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -31,6 +31,7 @@ import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsAccountForceWithdrawalBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; @@ -130,7 +131,7 @@ public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, } account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(), - depositAccountOnHoldTransactions, backdatedTxnsAllowedTill); + depositAccountOnHoldTransactions, backdatedTxnsAllowedTill, transactionBooleanValues.isForceWithdrawal()); saveTransactionToGenerateTransactionId(withdrawal); if (backdatedTxnsAllowedTill) { @@ -142,7 +143,11 @@ public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer(), backdatedTxnsAllowedTill); - businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal)); + if (transactionBooleanValues.isForceWithdrawal()) { + businessEventNotifierService.notifyPostBusinessEvent(new SavingsAccountForceWithdrawalBusinessEvent(withdrawal)); + } else { + businessEventNotifierService.notifyPostBusinessEvent(new SavingsWithdrawalBusinessEvent(withdrawal)); + } return withdrawal; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ForceWithdrawalSavingsAccountCommandHandler.java similarity index 68% rename from fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.java rename to fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ForceWithdrawalSavingsAccountCommandHandler.java index aab50d4f9ee..c0c96a38b89 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/mix/handler/UpdateTaxonomyMappingCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ForceWithdrawalSavingsAccountCommandHandler.java @@ -16,32 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.fineract.mix.handler; +package org.apache.fineract.portfolio.savings.handler; import org.apache.fineract.commands.annotation.CommandType; import org.apache.fineract.commands.handler.NewCommandSourceHandler; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.mix.service.MixTaxonomyMappingWritePlatformService; +import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@CommandType(entity = "XBRLMAPPING", action = "UPDATE") -public class UpdateTaxonomyMappingCommandHandler implements NewCommandSourceHandler { +@CommandType(entity = "SAVINGSACCOUNT", action = "FORCE_WITHDRAWAL") +public class ForceWithdrawalSavingsAccountCommandHandler implements NewCommandSourceHandler { - private final MixTaxonomyMappingWritePlatformService writeTaxonomyService; + private final SavingsAccountWritePlatformService writePlatformService; @Autowired - public UpdateTaxonomyMappingCommandHandler(final MixTaxonomyMappingWritePlatformService writeTaxonomyService) { - this.writeTaxonomyService = writeTaxonomyService; + public ForceWithdrawalSavingsAccountCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; } @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writeTaxonomyService.updateMapping(command.entityId(), command); + return this.writePlatformService.forceWithdrawal(command.getSavingsId(), command); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index fb1031e9531..1a10287ed2c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -1391,8 +1391,9 @@ public String retrieveAccountNumberByAccountId(Long accountId) { @Override public List getAccountsIdsByStatusPaged(Integer status, int pageSize, Long maxSavingsIdInList) { - String sql = new StringBuilder().append(" SELECT sa.id FROM m_savings_account sa ") - .append(" where sa.id > ? and sa.status_enum = ? ").append(" order by sa.id limit ?").toString(); + final String sql = """ + SELECT sa.id FROM m_savings_account sa + where sa.id > ? and sa.status_enum = ? order by sa.id limit ?\s"""; try { return this.jdbcTemplate.queryForList(sql, Long.class, new Object[] { maxSavingsIdInList, status, pageSize }); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index f30712b509c..c5335a438ce 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -405,6 +405,69 @@ public CommandProcessingResult withdrawal(final Long savingsId, final JsonComman .build(); } + @Transactional + @Override + public CommandProcessingResult forceWithdrawal(final Long savingsId, final JsonCommand command) { + + this.savingsAccountTransactionDataValidator.validate(command); + + boolean isGsim = false; + + final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); + + final Map changes = new LinkedHashMap<>(); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + + final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); + + final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); + + if (account.getGsim() != null) { + isGsim = true; + } + checkClientOrGroupActive(account); + + this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account); + + final boolean isAccountTransfer = false; + final boolean isRegularTransaction = true; + final boolean isApplyWithdrawFee = true; + final boolean isInterestTransfer = false; + final boolean isWithdrawBalance = false; + final boolean isForceWithdrawal = true; + final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, + isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance, isForceWithdrawal); + final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, + transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill); + + if (isGsim && (withdrawal.getId() != null)) { + GroupSavingsIndividualMonitoring gsim = gsimRepository.findById(account.getGsim().getId()).orElseThrow(); + BigDecimal currentBalance = gsim.getParentDeposit().subtract(transactionAmount); + gsim.setParentDeposit(currentBalance); + gsimRepository.save(gsim); + + } + + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.savingsTransactionNote(account, withdrawal, noteText); + this.noteRepository.save(note); + } + + return new CommandProcessingResultBuilder() // + .withEntityId(withdrawal.getId()) // + .withOfficeId(account.officeId()) // + .withClientId(account.clientId()) // + .withGroupId(account.groupId()) // + .withSavingsId(savingsId) // + .with(changes)// + .build(); + } + @Transactional @Override public CommandProcessingResult applyAnnualFee(final Long savingsAccountChargeId, final Long accountId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java index e04b3226b19..af383784982 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchReadPlatformServiceImpl.java @@ -38,12 +38,15 @@ import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; +import org.apache.fineract.portfolio.savings.data.SavingsAccountStatusEnumData; import org.apache.fineract.portfolio.savings.service.SavingsEnumerations; import org.apache.fineract.portfolio.search.SearchConstants; import org.apache.fineract.portfolio.search.data.AdHocQuerySearchConditions; import org.apache.fineract.portfolio.search.data.AdHocSearchQueryData; import org.apache.fineract.portfolio.search.data.SearchConditions; import org.apache.fineract.portfolio.search.data.SearchData; +import org.apache.fineract.portfolio.shareaccounts.data.ShareAccountStatusEnumData; +import org.apache.fineract.portfolio.shareaccounts.service.SharesEnumerations; import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -165,16 +168,20 @@ public SearchData mapRow(final ResultSet rs, @SuppressWarnings("unused") final i if (entityType.equalsIgnoreCase("client") || entityType.equalsIgnoreCase("clientidentifier")) { entityStatus = ClientEnumerations.status(entityStatusEnum); - } - - else if (entityType.equalsIgnoreCase("group") || entityType.equalsIgnoreCase("center")) { + } else if (entityType.equalsIgnoreCase("group") || entityType.equalsIgnoreCase("center")) { entityStatus = GroupingTypeEnumerations.status(entityStatusEnum); - } - - else if (entityType.equalsIgnoreCase("loan")) { + } else if (entityType.equalsIgnoreCase("loan")) { LoanStatusEnumData loanStatusEnumData = LoanEnumerations.status(entityStatusEnum); entityStatus = LoanEnumerations.status(loanStatusEnumData); + } else if (entityType.equalsIgnoreCase("saving")) { + SavingsAccountStatusEnumData savingsAccountStatusEnumData = SavingsEnumerations.status(entityStatusEnum); + + entityStatus = SavingsEnumerations.status(savingsAccountStatusEnumData); + } else if (entityType.equalsIgnoreCase("share")) { + ShareAccountStatusEnumData shareAccountStatusEnumData = SharesEnumerations.status(entityStatusEnum); + + entityStatus = SharesEnumerations.status(shareAccountStatusEnumData); } return new SearchData(entityId, entityAccountNo, entityExternalId, entityName, entityType, parentId, parentName, parentType, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java index ea0fc832cdb..ca88bf6a138 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfAccountTransferReadServiceImpl.java @@ -35,19 +35,20 @@ public class SelfAccountTransferReadServiceImpl implements SelfAccountTransferRe @Override public Collection retrieveSelfAccountTemplateData(AppUser user) { SelfAccountTemplateMapper mapper = new SelfAccountTemplateMapper(); - StringBuilder sql = new StringBuilder().append("select s.id as accountId, ").append("s.account_no as accountNo, ") - .append("2 as accountType, ").append("c.id as clientId, ").append("c.display_name as clientName, ") - .append("o.id as officeId, ").append("o.name as officeName ").append("from m_appuser as u ") - .append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ") - .append("inner join m_client as c on map.client_id = c.id ").append("inner join m_office as o on c.office_id = o.id ") - .append("inner join m_savings_account as s on s.client_id = c.id ").append("where u.id = ? ") - .append("and s.status_enum = 300 ").append("union ").append("select l.id as accountId, ") - .append("l.account_no as accountNo, ").append("1 as accountType, ").append("c.id as clientId, ") - .append("c.display_name as clientName, ").append("o.id as officeId, ").append("o.name as officeName ") - .append("from m_appuser as u ").append("inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id ") - .append("inner join m_client as c on map.client_id = c.id ").append("inner join m_office as o on c.office_id = o.id ") - .append("inner join m_loan as l on l.client_id = c.id ").append("where u.id = ? ").append("and l.loan_status_id = 300 "); - return this.jdbcTemplate.query(sql.toString(), mapper, new Object[] { user.getId(), user.getId() }); + final String sql = """ + select s.id as accountId, s.account_no as accountNo, + 2 as accountType, c.id as clientId, c.display_name as clientName, + o.id as officeId, o.name as officeName from m_appuser as u + inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id + inner join m_client as c on map.client_id = c.id inner join m_office as o on c.office_id = o.id + inner join m_savings_account as s on s.client_id = c.id where u.id = ? + and s.status_enum = 300 union select l.id as accountId, + l.account_no as accountNo, 1 as accountType, c.id as clientId, + c.display_name as clientName, o.id as officeId, o.name as officeName + from m_appuser as u inner join m_selfservice_user_client_mapping as map on u.id = map.appuser_id + inner join m_client as c on map.client_id = c.id inner join m_office as o on c.office_id = o.id + inner join m_loan as l on l.client_id = c.id where u.id = ? and l.loan_status_id = 300\s"""; + return this.jdbcTemplate.query(sql, mapper, new Object[] { user.getId(), user.getId() }); } private static final class SelfAccountTemplateMapper implements RowMapper { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfSavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfSavingsProductsApiResource.java index 4db3b2f7233..92a973ca2e6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfSavingsProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfSavingsProductsApiResource.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.self.products.api; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -50,6 +51,7 @@ public class SelfSavingsProductsApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Self Savings Products", operationId = "retrieveAllSelfSavingsProducts") public String retrieveAll(@QueryParam(SavingsApiConstants.clientIdParamName) final Long clientId, @Context final UriInfo uriInfo) { this.appUserClientMapperReadService.validateAppuserClientsMapping(clientId); @@ -61,6 +63,7 @@ public String retrieveAll(@QueryParam(SavingsApiConstants.clientIdParamName) fin @Path("{productId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Self Savings Product", operationId = "retrieveOneSelfSavingsProduct") public String retrieveOne(@PathParam(SavingsApiConstants.productIdParamName) final Long productId, @QueryParam(SavingsApiConstants.clientIdParamName) final Long clientId, @Context final UriInfo uriInfo) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java index e2987b0cb4e..5d895e2f30b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/savings/api/SelfSavingsApiResource.java @@ -121,8 +121,9 @@ public String retrieveSavingsTransaction(@PathParam("accountId") @Parameter(desc @Path("{accountId}/charges") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "List Savings Charges", description = "Lists Savings Charges\n\n" + "Example Requests:\n" + "\n" - + "self/savingsaccounts/1/charges\n" + "\n" + "self/savingsaccounts/1/charges?chargeStatus=inactive\n" + "\n" + @Operation(summary = "List Savings Charges", operationId = "retrieveAllSelfSavingsAccountCharges", description = "Lists Savings Charges\n\n" + + "Example Requests:\n" + "\n" + "self/savingsaccounts/1/charges\n" + "\n" + + "self/savingsaccounts/1/charges?chargeStatus=inactive\n" + "\n" + "self/savingsaccounts/1/charges?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SelfSavingsApiResourceSwagger.GetSelfSavingsAccountsAccountIdChargesResponse.class)))) public String retrieveAllSavingsAccountCharges(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @@ -138,8 +139,9 @@ public String retrieveAllSavingsAccountCharges(@PathParam("accountId") @Paramete @Path("{accountId}/charges/{savingsAccountChargeId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - @Operation(summary = "Retrieve a Savings account Charge", description = "Retrieves a Savings account Charge\n\n" + "Example Requests:\n" - + "\n" + "self/savingsaccounts/1/charges/5\n" + "\n" + "\n" + "self/savingsaccounts/1/charges/5?fields=name,amountOrPercentage") + @Operation(summary = "Retrieve a Savings account Charge", operationId = "retrieveSelfSavingsAccountCharge", description = "Retrieves a Savings account Charge\n\n" + + "Example Requests:\n" + "\n" + "self/savingsaccounts/1/charges/5\n" + "\n" + "\n" + + "self/savingsaccounts/1/charges/5?fields=name,amountOrPercentage") @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfSavingsApiResourceSwagger.GetSelfSavingsAccountsAccountIdChargesSavingsAccountChargeIdResponse.class))) public String retrieveSavingsAccountCharge(@PathParam("accountId") @Parameter(description = "accountId") final Long accountId, @PathParam("savingsAccountChargeId") @Parameter(description = "savingsAccountChargeId") final Long savingsAccountChargeId, @@ -161,6 +163,7 @@ private void validateAppuserSavingsAccountMapping(final Long accountId) { @GET @Path("template") @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Self Savings Account Template", operationId = "retrieveTemplateSelfSavingsAccount") public String template(@QueryParam("clientId") final Long clientId, @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java index bd277d6e83e..3c41df7b506 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/security/api/SelfAuthenticationApiResource.java @@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; @@ -53,7 +54,9 @@ public class SelfAuthenticationApiResource { @Operation(summary = "Verify authentication", description = "Authenticates the credentials provided and returns the set roles and permissions allowed.\n\n" + "Please visit this link for more info - https://fineract.apache.org/docs/legacy/#selfbasicauth") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationRequest.class))) - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfAuthenticationApiResourceSwagger.PostSelfAuthenticationResponse.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = SelfAuthenticationApiResourceSwagger.PostSelfAuthenticationResponse.class))), + @ApiResponse(responseCode = "403", description = "Password reset required") }) public String authenticate(final String apiRequestBodyAsJson) { return this.authenticationApiResource.authenticate(apiRequestBodyAsJson, true); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/spm/api/SelfSpmApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/spm/api/SelfSpmApiResource.java index 483b41da08d..10f380d2e94 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/spm/api/SelfSpmApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/spm/api/SelfSpmApiResource.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.self.spm.api; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; @@ -48,6 +49,7 @@ public class SelfSpmApiResource { @GET @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List all self surveys", operationId = "fetchAllSelfSurveys") @Transactional public List fetchAllSurveys() { securityContext.authenticatedUser(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index dc648318db0..bb9477fd01c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -29,6 +29,7 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -706,16 +707,8 @@ public Map validateAndApplyAddtionalShares(JsonCommand jsonComma } } boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(requestedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(requestedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname).value(requestedDate) .failWithCodeNoParameterAddedToErrorCode("purchase.transaction.date.cannot.be.before.existing.transactions"); @@ -732,6 +725,23 @@ public Map validateAndApplyAddtionalShares(JsonCommand jsonComma return actualChanges; } + private boolean isTransactionBeforeExistingTransactions(LocalDate requestedDate, boolean isTransactionBeforeExistingTransactions, + ShareAccount shareAccount) { + Collection activeTransactions = shareAccount.getShareAccountTransactions().stream() + .filter(tr -> tr.isActive() && !tr.isChargeTransaction() && !tr.isPurchaseRejectedTransaction()).toList(); + + for (ShareAccountTransaction transaction : activeTransactions) { + if (!transaction.isChargeTransaction() && transaction.isActive() && !transaction.isPurchaseRejectedTransaction()) { + LocalDate transactionDate = transaction.getPurchasedDate(); + if (DateUtils.isBefore(requestedDate, transactionDate)) { + isTransactionBeforeExistingTransactions = true; + break; + } + } + } + return isTransactionBeforeExistingTransactions; + } + private void handleAdditionalSharesChargeTransactions(final ShareAccount account, final ShareAccountTransaction purchaseTransaction) { Set charges = account.getCharges(); BigDecimal totalChargeAmount = BigDecimal.ZERO; @@ -863,16 +873,8 @@ public Map validateAndRedeemShares(JsonCommand jsonCommand, Shar baseDataValidator.reset().parameter(ShareAccountApiConstants.requestedshares_paramname).value(sharesRequested).notNull() .longGreaterThanZero(); boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction() && transaction.isActive()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(requestedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(requestedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.requesteddate_paramname).value(requestedDate) .failWithCodeNoParameterAddedToErrorCode("redeem.transaction.date.cannot.be.before.existing.transactions"); @@ -1018,16 +1020,8 @@ public Map validateAndClose(JsonCommand jsonCommand, ShareAccoun throw new PlatformApiDataValidationException(dataValidationErrors); } boolean isTransactionBeforeExistingTransactions = false; - Set transactions = account.getShareAccountTransactions(); - for (ShareAccountTransaction transaction : transactions) { - if (!transaction.isChargeTransaction()) { - LocalDate transactionDate = transaction.getPurchasedDate(); - if (DateUtils.isBefore(closedDate, transactionDate)) { - isTransactionBeforeExistingTransactions = true; - break; - } - } - } + isTransactionBeforeExistingTransactions = isTransactionBeforeExistingTransactions(closedDate, + isTransactionBeforeExistingTransactions, account); if (isTransactionBeforeExistingTransactions) { baseDataValidator.reset().parameter(ShareAccountApiConstants.closeddate_paramname).value(closedDate) .failWithCodeNoParameterAddedToErrorCode("share.account.cannot.be.closed.before.existing.transactions"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java index 7124ff7894a..2f7d851814e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountReadPlatformServiceImpl.java @@ -235,42 +235,41 @@ private static final class ShareAccountMapper implements RowMapper private final Collection charges; private final Collection purchasedShares; - private final String schema; + private static final String SHARE_ACCOUNT_SCHEMA = """ + sa.id as id, sa.external_id as externalId, sa.status_enum as statusEnum, + sa.savings_account_id, msa.account_no as savingsAccNo, + c.id as clientId, c.display_name as clientName, + sa.account_no as accountNo, sa.total_approved_shares as approvedShares, sa.total_pending_shares as pendingShares, + sa.savings_account_id as savingsAccountNo, sa.minimum_active_period_frequency as minimumactivePeriod, + sa.minimum_active_period_frequency_enum as minimumactivePeriodEnum, + sa.lockin_period_frequency as lockinPeriod, sa.lockin_period_frequency_enum as lockinPeriodEnum, + sa.allow_dividends_inactive_clients as allowdividendsforinactiveclients, + sa.submitted_date as submittedDate, sbu.username as submittedByUsername, + sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, + sa.rejected_date as rejectedDate, rbu.username as rejectedByUsername, + rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, + sa.approved_date as approvedDate, abu.username as approvedByUsername, + abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, + sa.activated_date as activatedDate, avbu.username as activatedByUsername, + avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, + sa.closed_date as closedDate, cbu.username as closedByUsername, + cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, + sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, + curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, + curr.display_symbol as currencyDisplaySymbol, sa.product_id as productId, p.name as productName, p.short_name as shortProductName + from m_share_account sa join m_share_product as p on p.id = sa.product_id + join m_currency curr on curr.code = sa.currency_code left join m_client c ON c.id = sa.client_id + left join m_appuser sbu on sbu.id = sa.submitted_userid + left join m_appuser rbu on rbu.id = sa.rejected_userid + left join m_appuser abu on abu.id = sa.approved_userid + left join m_appuser avbu on rbu.id = sa.activated_userid + left join m_appuser cbu on cbu.id = sa.closed_userid + left join m_savings_account msa on sa.savings_account_id = msa.id\s"""; ShareAccountMapper(final Collection charges, final Collection purchasedShares) { this.charges = charges; this.purchasedShares = purchasedShares; - StringBuilder buff = new StringBuilder().append("sa.id as id, sa.external_id as externalId, sa.status_enum as statusEnum, ") - .append("sa.savings_account_id, msa.account_no as savingsAccNo, ") - .append("c.id as clientId, c.display_name as clientName, ") - .append("sa.account_no as accountNo, sa.total_approved_shares as approvedShares, sa.total_pending_shares as pendingShares, ") - .append("sa.savings_account_id as savingsAccountNo, sa.minimum_active_period_frequency as minimumactivePeriod, ") - .append("sa.minimum_active_period_frequency_enum as minimumactivePeriodEnum, ") - .append("sa.lockin_period_frequency as lockinPeriod, sa.lockin_period_frequency_enum as lockinPeriodEnum, ") - .append("sa.allow_dividends_inactive_clients as allowdividendsforinactiveclients, ") - .append("sa.submitted_date as submittedDate, sbu.username as submittedByUsername, ") - .append("sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, ") - .append("sa.rejected_date as rejectedDate, rbu.username as rejectedByUsername, ") - .append("rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, ") - .append("sa.approved_date as approvedDate, abu.username as approvedByUsername, ") - .append("abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, ") - .append("sa.activated_date as activatedDate, avbu.username as activatedByUsername, ") - .append("avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, ") - .append("sa.closed_date as closedDate, cbu.username as closedByUsername, ") - .append("cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, ") - .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, ") - .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ") - .append("curr.display_symbol as currencyDisplaySymbol, sa.product_id as productId, p.name as productName, p.short_name as shortProductName ") - .append("from m_share_account sa ").append("join m_share_product as p on p.id = sa.product_id ") - .append("join m_currency curr on curr.code = sa.currency_code ").append("left join m_client c ON c.id = sa.client_id ") - .append("left join m_appuser sbu on sbu.id = sa.submitted_userid ") - .append("left join m_appuser rbu on rbu.id = sa.rejected_userid ") - .append("left join m_appuser abu on abu.id = sa.approved_userid ") - .append("left join m_appuser avbu on rbu.id = sa.activated_userid ") - .append("left join m_appuser cbu on cbu.id = sa.closed_userid ") - .append("left join m_savings_account msa on sa.savings_account_id = msa.id "); - this.schema = buff.toString(); } @Override @@ -356,7 +355,7 @@ public ShareAccountData mapRow(ResultSet rs, @SuppressWarnings("unused") int row } public String schema() { - return this.schema; + return SHARE_ACCOUNT_SCHEMA; } } @@ -366,17 +365,15 @@ private static final class ShareAccountMapperForDividents implements RowMapper { - private final String schema; - - PurchasedSharesDataRowMapper() { - StringBuilder buff = new StringBuilder().append( - "saps.id as purchasedId, saps.account_id as accountId, saps.transaction_date as transactionDate, saps.total_shares as purchasedShares, saps.unit_price as unitPrice, ") - .append("saps.status_enum as purchaseStatus, saps.type_enum as purchaseType, saps.amount as amount, saps.charge_amount as chargeamount, ") - .append("saps.amount_paid as amountPaid "); + private static final String PURCHASED_SHARES_SCHEMA = """ + saps.id as purchasedId, saps.account_id as accountId, saps.transaction_date as transactionDate, saps.total_shares as purchasedShares, saps.unit_price as unitPrice, + saps.status_enum as purchaseStatus, saps.type_enum as purchaseType, saps.amount as amount, saps.charge_amount as chargeamount, + saps.amount_paid as amountPaid\s"""; - schema = buff.toString(); - } + PurchasedSharesDataRowMapper() {} @Override public ShareAccountTransactionData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException { @@ -457,21 +450,18 @@ public ShareAccountTransactionData mapRow(ResultSet rs, @SuppressWarnings("unuse } public String schema() { - return this.schema; + return PURCHASED_SHARES_SCHEMA; } } private static final class ShareAccountDividendRowMapper implements RowMapper { - private final String schema; + private static final String SHARE_ACCOUNT_DIVIDEND_SCHEMA = """ + spdp.created_date, sadd.id, sadd.amount, sadd.savings_transaction_id, sadd.status + from m_share_account_dividend_details sadd + JOIN m_share_product_dividend_pay_out spdp ON spdp.id = sadd.dividend_pay_out_id\s"""; - ShareAccountDividendRowMapper() { - StringBuilder buff = new StringBuilder() - .append("spdp.created_date, sadd.id, sadd.amount, sadd.savings_transaction_id, sadd.status ") - .append(" from m_share_account_dividend_details sadd ") - .append("JOIN m_share_product_dividend_pay_out spdp ON spdp.id = sadd.dividend_pay_out_id "); - schema = buff.toString(); - } + ShareAccountDividendRowMapper() {} @SuppressWarnings("unused") @Override @@ -487,7 +477,7 @@ public ShareAccountDividendData mapRow(ResultSet rs, int rowNum) throws SQLExcep } public String schema() { - return this.schema; + return SHARE_ACCOUNT_DIVIDEND_SCHEMA; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/SharesEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/SharesEnumerations.java index 596d93fedb5..efe1998727a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/SharesEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/SharesEnumerations.java @@ -32,6 +32,15 @@ private SharesEnumerations() { } + public static EnumOptionData status(final ShareAccountStatusEnumData status) { + + Long id = status.getId(); + String code = status.getCode(); + String value = status.getValue(); + + return new EnumOptionData(id, code, value); + } + public static ShareAccountStatusEnumData status(final Integer statusEnum) { return status(ShareAccountStatusType.fromInt(statusEnum)); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java index c8c6ff12065..abc98508df4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/api/SpmApiResource.java @@ -62,7 +62,7 @@ public class SpmApiResource { @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @Transactional - @Operation(summary = "List all Surveys", description = "") + @Operation(summary = "List all Surveys", operationId = "fetchAllSurveys", description = "") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SurveyData.class)))) }) public List fetchAllSurveys(@QueryParam("isActive") final Boolean isActive) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java index d458ff12c05..094bcac7209 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/spm/service/ScorecardReadPlatformServiceImpl.java @@ -39,16 +39,16 @@ public class ScorecardReadPlatformServiceImpl implements ScorecardReadPlatformSe private static final class ScorecardMapper implements RowMapper { + private static final String SCORECARD_SCHEMA = """ + sc.id as id, sc.survey_id as surveyId, s.a_name as surveyName, + sc.client_id as clientId, + sc.user_id as userId, user.username as username + from m_survey_scorecards sc + left join m_surveys s ON s.id = sc.survey_id + left join m_appuser user ON user.id = sc.user_id\s"""; + public String schema() { - StringBuilder sb = new StringBuilder(50); - sb.append(" sc.id as id, sc.survey_id as surveyId, s.a_name as surveyName, "); - sb.append(" sc.client_id as clientId,"); - sb.append(" sc.user_id as userId, user.username as username "); - sb.append(" from m_survey_scorecards sc "); - sb.append(" left join m_surveys s ON s.id = sc.survey_id "); - sb.append(" left join m_appuser user ON user.id = sc.user_id "); - - return sb.toString(); + return SCORECARD_SCHEMA; } @Override @@ -67,14 +67,14 @@ public ScorecardData mapRow(final ResultSet rs, @SuppressWarnings("unused") fina private static final class ScorecardValueMapper implements RowMapper { - public String schema() { - StringBuilder sb = new StringBuilder(50); - sb.append(" sc.question_id as questionId, sc.response_id as responseId, "); - sb.append(" sc.created_on as createdOn, sc.a_value as value "); - sb.append(" from m_survey_scorecards sc "); - sb.append(" where sc.survey_id = ? and sc.client_id = ? "); + private static final String SCORECARD_VALUE_SCHEMA = """ + sc.question_id as questionId, sc.response_id as responseId, + sc.created_on as createdOn, sc.a_value as value + from m_survey_scorecards sc + where sc.survey_id = ? and sc.client_id = ?\s"""; - return sb.toString(); + public String schema() { + return SCORECARD_VALUE_SCHEMA; } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java index 794a95eca76..61dafd4df6c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/api/UsersApiResource.java @@ -90,8 +90,8 @@ public class UsersApiResource { private final BulkImportWorkbookService bulkImportWorkbookService; @GET - @Operation(summary = "Retrieve list of users", description = "Example Requests:\n" + "\n" + "users\n" + "\n" + "\n" - + "users?fields=id,username,email,officeName") + @Operation(summary = "Retrieve list of users", operationId = "retrieveAllUsers", description = "Example Requests:\n" + "\n" + "users\n" + + "\n" + "\n" + "users?fields=id,username,email,officeName") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = UsersApiResourceSwagger.GetUsersResponse.class)))) }) @Consumes({ MediaType.APPLICATION_JSON }) @@ -108,8 +108,8 @@ public String retrieveAll(@Context final UriInfo uriInfo) { @GET @Path("{userId}") - @Operation(summary = "Retrieve a User", description = "Example Requests:\n" + "\n" + "users/1\n" + "\n" + "\n" - + "users/1?template=true\n" + "\n" + "\n" + "users/1?fields=username,officeName") + @Operation(summary = "Retrieve a User", operationId = "retrieveOneUser", description = "Example Requests:\n" + "\n" + "users/1\n" + "\n" + + "\n" + "users/1?template=true\n" + "\n" + "\n" + "users/1?fields=username,officeName") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.GetUsersUserIdResponse.class))) }) @Consumes({ MediaType.APPLICATION_JSON }) @@ -131,7 +131,7 @@ public String retrieveOne(@PathParam("userId") @Parameter(description = "userId" @GET @Path("template") - @Operation(summary = "Retrieve User Details Template", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + @Operation(summary = "Retrieve User Details Template", operationId = "retrieveTemplateUser", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + "users/template") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.GetUsersTemplateResponse.class))) }) @@ -148,7 +148,7 @@ public String template(@Context final UriInfo uriInfo) { } @POST - @Operation(summary = "Create a User", description = "Adds new application user.\n" + "\n" + @Operation(summary = "Create a User", operationId = "createUser", description = "Adds new application user.\n" + "\n" + "Note: Password information is not required (or processed). Password details at present are auto-generated and then sent to the email account given (which is why it can take a few seconds to complete).\n" + "\n" + "Mandatory Fields: \n" + "username, firstname, lastname, email, officeId, roles, sendPasswordToEmail\n" + "\n" + "Optional Fields: \n" + "staffId,passwordNeverExpires,isSelfServiceUser,clients") @@ -171,7 +171,7 @@ public String create(@Parameter(hidden = true) final String apiRequestBodyAsJson @PUT @Path("{userId}") - @Operation(summary = "Update a User", description = "Updates the user") + @Operation(summary = "Update a User", operationId = "updateUser", description = "Updates the user") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.PutUsersUserIdResponse.class))) }) @@ -192,7 +192,7 @@ public String update(@PathParam("userId") @Parameter(description = "userId") fin @POST @Path("{userId}/pwd") - @Operation(summary = "Change the password of a User", description = "When updating a password you must provide the repeatPassword parameter also.") + @Operation(summary = "Change the password of a User", operationId = "changePasswordUser", description = "When updating a password you must provide the repeatPassword parameter also.") @RequestBody(required = true, content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.ChangePwdUsersUserIdRequest.class))) @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.ChangePwdUsersUserIdResponse.class))) }) @@ -213,7 +213,7 @@ public String changePassword(@PathParam("userId") @Parameter(description = "user @DELETE @Path("{userId}") - @Operation(summary = "Delete a User", description = "Removes the user and the associated roles and permissions.") + @Operation(summary = "Delete a User", operationId = "deleteUser", description = "Removes the user and the associated roles and permissions.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UsersApiResourceSwagger.DeleteUsersUserIdResponse.class))) }) @Consumes({ MediaType.APPLICATION_JSON }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java index 9c5ec5c80d5..ccf0e7b6e3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java @@ -127,6 +127,9 @@ public CommandProcessingResult createUser(final JsonCommand command) { } AppUser appUser = AppUser.fromJson(userOffice, linkedStaff, allRoles, clients, command); + if (this.configurationDomainService.isForcePasswordResetOnFirstLoginEnabled()) { + appUser.updatePasswordResetRequired(true); + } final Boolean sendPasswordToEmail = command.booleanObjectValueOfParameterNamed("sendPasswordToEmail"); this.userDomainService.create(appUser, sendPasswordToEmail); @@ -160,12 +163,14 @@ public CommandProcessingResult createUser(final JsonCommand command) { @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) }) public CommandProcessingResult changeUserPassword(final Long userId, final JsonCommand command) { try { - this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build()); - this.fromApiJsonDeserializer.validateForChangePassword(command.json(), this.context.authenticatedUser()); + this.context.authenticatedUser(new CommandWrapperBuilder().changeUserPassword(userId).build()); + this.fromApiJsonDeserializer.validateForChangePassword(command.json(), + this.context.authenticatedUser(new CommandWrapperBuilder().changeUserPassword(userId).build())); final AppUser userToUpdate = this.appUserRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId)); final AppUserPreviousPassword currentPasswordToSaveAsPreview = getCurrentPasswordToSaveAsPreview(userToUpdate, command); final Map changes = userToUpdate.changePassword(command, this.platformPasswordEncoder); if (!changes.isEmpty()) { + userToUpdate.updatePasswordResetRequired(false); this.appUserRepository.saveAndFlush(userToUpdate); if (currentPasswordToSaveAsPreview != null) { this.appUserPreviewPasswordRepository.save(currentPasswordToSaveAsPreview); @@ -190,9 +195,9 @@ public CommandProcessingResult changeUserPassword(final Long userId, final JsonC @Caching(evict = { @CacheEvict(value = "users", allEntries = true), @CacheEvict(value = "usersByUsername", allEntries = true) }) public CommandProcessingResult updateUser(final Long userId, final JsonCommand command) { try { - this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build()); + final AppUser currentUser = this.context.authenticatedUser(new CommandWrapperBuilder().updateUser(null).build()); - this.fromApiJsonDeserializer.validateForUpdate(command.json(), this.context.authenticatedUser()); + this.fromApiJsonDeserializer.validateForUpdate(command.json(), currentUser); final AppUser userToUpdate = this.appUserRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId)); @@ -238,6 +243,10 @@ public CommandProcessingResult updateUser(final Long userId, final JsonCommand c } if (!changes.isEmpty()) { + if ((changes.containsKey("password") || changes.containsKey("passwordEncoded")) && !currentUser.getId().equals(userId) + && this.configurationDomainService.isForcePasswordResetOnFirstLoginEnabled()) { + userToUpdate.updatePasswordResetRequired(true); + } this.appUserRepository.saveAndFlush(userToUpdate); if (currentPasswordToSaveAsPreview != null) { diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 78a86a40be4..02d0325ba38 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -92,7 +92,7 @@ fineract.partitioned-job.partitioned-job-properties[0].thread-pool-core-pool-siz fineract.partitioned-job.partitioned-job-properties[0].thread-pool-max-pool-size=${LOAN_COB_THREAD_POOL_MAX_POOL_SIZE:5} fineract.partitioned-job.partitioned-job-properties[0].thread-pool-queue-capacity=${LOAN_COB_THREAD_POOL_QUEUE_CAPACITY:20} fineract.partitioned-job.partitioned-job-properties[0].retry-limit=${LOAN_COB_RETRY_LIMIT:5} -fineract.partitioned-job.partitioned-job-properties[0].poll-interval=${LOAN_COB_POLL_INTERVAL:10000} +fineract.partitioned-job.partitioned-job-properties[0].poll-interval=${LOAN_COB_POLL_INTERVAL:500} fineract.remote-job-message-handler.spring-events.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED:true} fineract.remote-job-message-handler.jms.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED:false} @@ -597,6 +597,14 @@ resilience4j.retry.instances.commandPaymentTypeDelete.enable-exponential-backoff resilience4j.retry.instances.commandPaymentTypeDelete.exponential-backoff-multiplier=${FINERACT_COMMAND_PAYMENT_TYPE_DELETE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2} resilience4j.retry.instances.commandPaymentTypeDelete.retryExceptions=${FINERACT_COMMAND_PAYMENT_TYPE_DELETE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException} +# mix taxonomy + +resilience4j.retry.instances.commandMixTaxonomyMappingUpdate.max-attempts=${FINERACT_COMMAND_MIX_TAXONOMY_MAPPING_UPDATE_RETRY_MAX_ATTEMPTS:3} +resilience4j.retry.instances.commandMixTaxonomyMappingUpdate.wait-duration=${FINERACT_COMMAND_MIX_TAXONOMY_MAPPING_UPDATE_RETRY_WAIT_DURATION:1s} +resilience4j.retry.instances.commandMixTaxonomyMappingUpdate.enable-exponential-backoff=${FINERACT_COMMAND_MIX_TAXONOMY_MAPPING_UPDATE_RETRY_ENABLE_EXPONENTIAL_BACKOFF:true} +resilience4j.retry.instances.commandMixTaxonomyMappingUpdate.exponential-backoff-multiplier=${FINERACT_COMMAND_MIX_TAXONOMY_MAPPING_UPDATE_RETRY_EXPONENTIAL_BACKOFF_MULTIPLIER:2} +resilience4j.retry.instances.commandMixTaxonomyMappingUpdate.retryExceptions=${FINERACT_COMMAND_MIX_TAXONOMY_MAPPING_UPDATE_RETRY_EXCEPTIONS:org.springframework.dao.ConcurrencyFailureException,org.eclipse.persistence.exceptions.OptimisticLockException,jakarta.persistence.OptimisticLockException,org.springframework.orm.jpa.JpaOptimisticLockingFailureException} + # command fineract.command.enabled=true diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml index de38521dae5..7d5a94e7835 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml @@ -31,4 +31,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml new file mode 100644 index 00000000000..7825c416067 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml @@ -0,0 +1,28 @@ + + + + + ALTER DATABASE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 297426a0ac5..7ad31493228 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -230,4 +230,11 @@ + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml index a4037a9faee..3e5d5c5157a 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml @@ -3461,6 +3461,7 @@ + ANY @@ -7821,86 +7822,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -7909,14 +7830,6 @@ - - - - - - - - @@ -7981,30 +7894,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -8029,38 +7918,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -8085,46 +7942,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -8133,22 +7950,6 @@ - - - - - - - - - - - - - - - - @@ -8597,14 +8398,6 @@ - - - - - - - - @@ -12686,6 +12479,7 @@ + ANY @@ -12872,42 +12666,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -13039,30 +12797,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -13076,18 +12810,6 @@ - - - - - - - - - - - - @@ -13098,7 +12820,7 @@ value=" /* Active Client is a client linked to the 'group' via m_group_client and with an active 'status_enum'.) Active Borrowers - Borrower may be a client or a 'group' */ select x.* from m_office o, m_group g, (select a.activeClients, (b.activeClientLoans + c.activeGroupLoans) as activeLoans, b.activeClientLoans, c.activeGroupLoans, (b.activeClientBorrowers + c.activeGroupBorrowers) as activeBorrowers, b.activeClientBorrowers, c.activeGroupBorrowers, (b.overdueClientLoans + c.overdueGroupLoans) as overdueLoans, b.overdueClientLoans, c.overdueGroupLoans from (select count(*) as activeClients from m_group topgroup join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%') join m_group_client gc on gc.group_id = g.id join m_client c on c.id = gc.client_id where topgroup.id = ${groupId} and c.status_enum = 300) a, (select count(*) as activeClientLoans, count(distinct(l.client_id)) as activeClientBorrowers, ifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueClientLoans from m_group topgroup join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%') join m_loan l on l.group_id = g.id and l.client_id is not null left join m_loan_arrears_aging laa on laa.loan_id = l.id where topgroup.id = ${groupId} and l.loan_status_id = 300) b, (select count(*) as activeGroupLoans, count(distinct(l.group_id)) as activeGroupBorrowers, ifnull(sum(if(laa.loan_id is not null, 1, 0)), 0) as overdueGroupLoans from m_group topgroup join m_group g on g.hierarchy like concat(topgroup.hierarchy, '%') join m_loan l on l.group_id = g.id and l.client_id is null left join m_loan_arrears_aging laa on laa.loan_id = l.id where topgroup.id = ${groupId} and l.loan_status_id = 300) c ) x where g.id = ${groupId} and o.id = g.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') "/> - + @@ -13112,7 +12834,7 @@ - + @@ -13126,7 +12848,7 @@ - + @@ -13139,7 +12861,7 @@ value=" select ifnull(cur.display_symbol, l.currency_code) as Currency, /*This query will return more than one entry if more than one currency is used */ count(distinct(c.id)) as activeClients, count(*) as activeLoans, sum(l.principal_disbursed_derived) as disbursedAmount, sum(l.principal_outstanding_derived) as loanOutstandingAmount, round((sum(l.principal_outstanding_derived) * 100) / sum(l.principal_disbursed_derived),2) as loanOutstandingPC, sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance, sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) as portfolioAtRisk, round((sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC, count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) as clientsInDefault, round((count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC, (sum(l.principal_disbursed_derived) / count(*)) as averageLoanAmount from m_staff fa join m_office o on o.id = fa.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_group pgm on pgm.staff_id = fa.id join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id join m_client c on c.id = l.client_id where fa.id = ${staffId} and l.loan_status_id = 300 group by l.currency_code "/> - + @@ -13152,7 +12874,7 @@ value=" select pgm.id, pgm.display_name as `name`, sts.enum_message_property as status from m_group pgm join m_office o on o.id = pgm.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') left join r_enum_value sts on sts.enum_name = 'status_enum' and sts.enum_id = pgm.status_enum where pgm.staff_id = ${staffId} "/> - + @@ -13165,7 +12887,7 @@ value=" select l.id as loanId, l.account_no as loanAccountNo, c.id as clientId, c.account_no as clientAccountNo, pgm.display_name as programName, (select count(*) from m_loan cy where cy.group_id = pgm.id and cy.client_id =c.id and cy.disbursedon_date <= l.disbursedon_date) as loanCycleNo, c.display_name as clientDisplayName, ifnull(cur.display_symbol, l.currency_code) as Currency, ifnull(l.principal_repaid_derived,0.0) as loanRepaidAmount, ifnull(l.principal_outstanding_derived, 0.0) as loanOutstandingAmount, ifnull(lpa.principal_in_advance_derived,0.0) as LoanPaidInAdvance, ifnull(laa.principal_overdue_derived, 0.0) as loanInArrearsAmount, if(ifnull(laa.principal_overdue_derived, 0.00) > 0, 'Yes', 'No') as inDefault, if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0) as portfolioAtRisk from m_group pgm join m_office o on o.id = pgm.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code join m_client c on c.id = l.client_id left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id where pgm.id = ${programId} and l.loan_status_id = 300 order by c.display_name, l.account_no "/> - + @@ -13178,7 +12900,7 @@ value=" select s.id, s.display_name, s.firstname, s.lastname, s.organisational_role_enum, s.organisational_role_parent_staff_id, sp.display_name as `organisational_role_parent_staff_display_name` from m_staff s join m_staff sp on s.organisational_role_parent_staff_id = sp.id where s.organisational_role_parent_staff_id = ${staffId} "/> - + @@ -13191,7 +12913,7 @@ value=" select ifnull(cur.display_symbol, l.currency_code) as Currency, /*This query will return more than one entry if more than one currency is used */ count(distinct(c.id)) as activeClients, count(*) as activeLoans, sum(l.principal_disbursed_derived) as disbursedAmount, sum(l.principal_outstanding_derived) as loanOutstandingAmount, round((sum(l.principal_outstanding_derived) * 100) / sum(l.principal_disbursed_derived),2) as loanOutstandingPC, sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance, sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) as portfolioAtRisk, round((sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC, count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) as clientsInDefault, round((count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC, (sum(l.principal_disbursed_derived) / count(*)) as averageLoanAmount from m_staff coord join m_staff fa on fa.organisational_role_parent_staff_id = coord.id join m_office o on o.id = fa.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_group pgm on pgm.staff_id = fa.id join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id join m_client c on c.id = l.client_id where coord.id = ${staffId} and l.loan_status_id = 300 group by l.currency_code "/> - + @@ -13204,7 +12926,7 @@ value=" select ifnull(cur.display_symbol, l.currency_code) as Currency, /*This query will return more than one entry if more than one currency is used */ count(distinct(c.id)) as activeClients, count(*) as activeLoans, sum(l.principal_disbursed_derived) as disbursedAmount, sum(l.principal_outstanding_derived) as loanOutstandingAmount, round((sum(l.principal_outstanding_derived) * 100) / sum(l.principal_disbursed_derived),2) as loanOutstandingPC, sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance, sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) as portfolioAtRisk, round((sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC, count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) as clientsInDefault, round((count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC, (sum(l.principal_disbursed_derived) / count(*)) as averageLoanAmount from m_staff bm join m_staff coord on coord.organisational_role_parent_staff_id = bm.id join m_staff fa on fa.organisational_role_parent_staff_id = coord.id join m_office o on o.id = fa.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_group pgm on pgm.staff_id = fa.id join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id join m_client c on c.id = l.client_id where bm.id = ${staffId} and l.loan_status_id = 300 group by l.currency_code "/> - + @@ -13217,7 +12939,7 @@ value=" select ifnull(cur.display_symbol, l.currency_code) as Currency, /*This query will return more than one entry if more than one currency is used */ count(distinct(c.id)) as activeClients, count(*) as activeLoans, sum(l.principal_disbursed_derived) as disbursedAmount, sum(l.principal_outstanding_derived) as loanOutstandingAmount, round((sum(l.principal_outstanding_derived) * 100) / sum(l.principal_disbursed_derived),2) as loanOutstandingPC, sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance, sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) as portfolioAtRisk, round((sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC, count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) as clientsInDefault, round((count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC, (sum(l.principal_disbursed_derived) / count(*)) as averageLoanAmount from m_staff pd join m_staff bm on bm.organisational_role_parent_staff_id = pd.id join m_staff coord on coord.organisational_role_parent_staff_id = bm.id join m_staff fa on fa.organisational_role_parent_staff_id = coord.id join m_office o on o.id = fa.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_group pgm on pgm.staff_id = fa.id join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id join m_client c on c.id = l.client_id where pd.id = ${staffId} and l.loan_status_id = 300 group by l.currency_code "/> - + @@ -13230,7 +12952,7 @@ value=" select ifnull(cur.display_symbol, l.currency_code) as Currency, /*This query will return more than one entry if more than one currency is used */ count(distinct(c.id)) as activeClients, count(*) as activeLoans, sum(l.principal_disbursed_derived) as disbursedAmount, sum(l.principal_outstanding_derived) as loanOutstandingAmount, round((sum(l.principal_outstanding_derived) * 100) / sum(l.principal_disbursed_derived),2) as loanOutstandingPC, sum(ifnull(lpa.principal_in_advance_derived,0.0)) as LoanPaidInAdvance, sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) as portfolioAtRisk, round((sum( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), l.principal_outstanding_derived,0)) * 100) / sum(l.principal_outstanding_derived), 2) as portfolioAtRiskPC, count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) as clientsInDefault, round((count(distinct( if(date_sub(curdate(), interval 28 day) > ifnull(laa.overdue_since_date_derived, curdate()), c.id,null))) * 100) / count(distinct(c.id)),2) as clientsInDefaultPC, (sum(l.principal_disbursed_derived) / count(*)) as averageLoanAmount from m_group pgm join m_office o on o.id = pgm.office_id and o.hierarchy like concat('${currentUserHierarchy}', '%') join m_loan l on l.group_id = pgm.id and l.client_id is not null left join m_currency cur on cur.code = l.currency_code left join m_loan_arrears_aging laa on laa.loan_id = l.id left join m_loan_paid_in_advance lpa on lpa.loan_id = l.id join m_client c on c.id = l.client_id where pgm.id = ${programId} and l.loan_status_id = 300 group by l.currency_code "/> - + @@ -13243,7 +12965,7 @@ value="SELECT x.* FROM m_client c, m_office o, ( SELECT a.loanCycle, a.activeLoans, b.lastLoanAmount, d.activeSavings, d.totalSavings FROM (SELECT IFNULL(MAX(l.loan_counter),0) AS loanCycle, COUNT(l.id) AS activeLoans FROM m_loan l WHERE l.loan_status_id=300 AND l.client_id=${clientId}) a, (SELECT count(l.id), IFNULL(l.principal_amount,0) AS 'lastLoanAmount' FROM m_loan l WHERE l.client_id=${clientId} AND l.disbursedon_date = (SELECT IFNULL(MAX(disbursedon_date),NOW()) FROM m_loan where client_id=${clientId} and loan_status_id=300) group by l.principal_amount) b, (SELECT COUNT(s.id) AS 'activeSavings', IFNULL(SUM(s.account_balance_derived),0) AS 'totalSavings' FROM m_savings_account s WHERE s.status_enum=300 AND s.client_id=${clientId}) d ) x WHERE c.id=${clientId} AND o.id = c.office_id AND o.hierarchy LIKE CONCAT('${currentUserHierarchy}', '%')"/> - + @@ -13256,7 +12978,7 @@ value="SELECT lp.name AS 'productName', MAX(l.loan_product_counter) AS 'loanProductCycle' FROM m_loan l JOIN m_product_loan lp ON l.product_id=lp.id WHERE lp.include_in_borrower_cycle=1 AND l.loan_product_counter IS NOT NULL AND l.client_id=${clientId} GROUP BY l.product_id"/> - + @@ -13270,2820 +12992,1344 @@ - - - - - - - - - - - - - + - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + - + - - - - + + + + - - + + - + - - - - + + + + - - + + - + - - - - + + + + - - + + - + - - - - + + + + - - + + - + - - - - + + + + - - + + - + - - - + + + - - - + + + - - - - - - - - + + + + + + + + - - - - + + + + - - - + + + - - - - + + + + - - - + + + - - - - + + + + - - - - - - - - - - - - - - - + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - + + + + - - - + + + - - - - + + + + - - - + + + - - - - + + + + - - - + + + - - - - - - - - + + + + + + + + - - - - + + + + - - - + + + - - - - + + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + - + - - - - - + + + + + - - - + value="SELECT mc.id, mc.firstname, mc.middlename as middlename, mc.lastname, mc.display_name as FullName, mc.mobile_no as mobileNo, mc.group_name as GroupName, mo.name as officename, ml.id as loanId, ml.account_no as accountnumber, ml.principal_amount_proposed as loanamount, ml.annual_nominal_interest_rate as annualinterestrate FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%') AND ounder.hierarchy like CONCAT('.', '%') LEFT JOIN ( select ml.id as loanId, ifnull(mc.id,mc2.id) as id, ifnull(mc.firstname,mc2.firstname) as firstname, ifnull(mc.middlename,ifnull(mc2.middlename,(''))) as middlename, ifnull(mc.lastname,mc2.lastname) as lastname, ifnull(mc.display_name,mc2.display_name) as display_name, ifnull(mc.status_enum,mc2.status_enum) as status_enum, ifnull(mc.mobile_no,mc2.mobile_no) as mobile_no, ifnull(mg.office_id,mc2.office_id) as office_id, ifnull(mg.staff_id,mc2.staff_id) as staff_id, mg.id as group_id, mg.display_name as group_name from m_loan ml left join m_group mg on mg.id = ml.group_id left join m_group_client mgc on mgc.group_id = mg.id left join m_client mc on mc.id = mgc.client_id left join m_client mc2 on mc2.id = ml.client_id order by loanId ) mc on mc.office_id = ounder.id left join m_loan ml on ml.id = mc.loanId WHERE mc.status_enum = 300 and mc.mobile_no is not null and (mo.id = ${officeId} or ${officeId} = -1) and (mc.staff_id = ${loanOfficerId} or ${loanOfficerId} = -1) and (ml.id = ${loanId} or ${loanId} = -1) and (mc.id = ${clientId} or ${clientId} = -1) and (mc.group_id = ${groupId} or ${groupId} = -1) and (ml.loan_type_enum = ${loanType} or ${loanType} = -1)"/> + + + - - - - + + + + - - - + value="SELECT c.id AS "id", c.firstname AS "firstName", c.middlename AS "middleName", c.lastname AS "lastName", c.display_name AS "fullName", c.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH( REPLACE(ounder.`hierarchy`, '.', '')) - 1))), ounder.`name`) AS "officeName", o.id AS "officeNumber" FROM m_office o JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, '%') JOIN m_client c ON c.office_id = ounder.id LEFT JOIN r_enum_value r ON r.enum_name = 'status_enum' AND r.enum_id = c.status_enum WHERE o.id = ${officeId} AND c.id = ${clientId} AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId})"/> + + + - - - - + + + + - - - + value="SELECT c.id AS "id", c.firstname AS "firstName", c.middlename AS "middleName", c.lastname AS "lastName", c.display_name AS "fullName", c.mobile_no AS "mobileNo", CONCAT(REPEAT("..", ((LENGTH(ounder.`hierarchy`) - LENGTH( REPLACE(ounder.`hierarchy`, '.', '')) - 1))), ounder.`name`) AS "officeName", o.id AS "officeNumber" FROM m_office o JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, '%') JOIN m_client c ON c.office_id = ounder.id LEFT JOIN r_enum_value r ON r.enum_name = 'status_enum' AND r.enum_id = c.status_enum WHERE o.id = ${officeId} AND c.id = ${clientId} AND (IFNULL(c.staff_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId})"/> + + + - - - - - + + + + + - - - + value="SELECT c.id AS "id", c.firstname AS "firstName", c.middlename AS "middleName", c.lastname AS "lastName", c.display_name AS "fullName", c.mobile_no AS "mobileNo", s.account_no AS "savingsAccountNo", ounder.id AS "officeNumber", ounder.name AS "officeName" FROM m_office o JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, '%') JOIN m_client c ON c.office_id = ounder.id JOIN m_savings_account s ON s.client_id = c.id JOIN m_savings_product sp ON sp.id = s.product_id LEFT JOIN m_staff st ON st.id = s.field_officer_id LEFT JOIN m_currency cur ON cur.code = s.currency_code WHERE o.id = ${officeId} AND (IFNULL(s.field_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND s.id = ${savingsId}"/> + + + - - - - - + + + + + - - - + value="SELECT c.id AS "id", c.firstname AS "firstName", c.middlename AS "middleName", c.lastname AS "lastName", c.display_name AS "fullName", c.mobile_no AS "mobileNo", s.account_no AS "savingsAccountNo", ounder.id AS "officeNumber", ounder.name AS "officeName" FROM m_office o JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(o.hierarchy, '%') JOIN m_client c ON c.office_id = ounder.id JOIN m_savings_account s ON s.client_id = c.id JOIN m_savings_product sp ON sp.id = s.product_id LEFT JOIN m_staff st ON st.id = s.field_officer_id LEFT JOIN m_currency cur ON cur.code = s.currency_code WHERE o.id = ${officeId} AND (IFNULL(s.field_officer_id, -10) = ${loanOfficerId} OR "-1" = ${loanOfficerId}) AND s.id = ${savingsId}"/> + + + - - - - - + + + + + - - - + value="SELECT sc.savingsId AS savingsId, sc.id AS clientId, sc.firstname, IFNULL(sc.middlename,'') AS middlename, sc.lastname, sc.display_name AS FullName, sc.mobile_no AS mobileNo, ms.`account_no` AS savingsAccountNo, ROUND(mst.amountPaid, ms.currency_digits) AS depositAmount, ms.account_balance_derived AS balance, mst.transactionDate AS transactionDate FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%') AND ounder.hierarchy LIKE CONCAT('.', '%') LEFT JOIN ( SELECT sa.id AS savingsId, mc.id AS id, mc.firstname AS firstname, mc.middlename AS middlename, mc.lastname AS lastname, mc.display_name AS display_name, mc.status_enum AS status_enum, mc.mobile_no AS mobile_no, mc.office_id AS office_id, mc.staff_id AS staff_id FROM m_savings_account sa LEFT JOIN m_client mc ON mc.id = sa.client_id ORDER BY savingsId) sc ON sc.office_id = ounder.id RIGHT JOIN m_savings_account AS ms ON sc.savingsId = ms.id RIGHT JOIN( SELECT st.amount AS amountPaid, st.id, st.savings_account_id, st.id AS savingsTransactionId, st.transaction_date AS transactionDate FROM m_savings_account_transaction st WHERE st.is_reversed = 0 GROUP BY st.savings_account_id ) AS mst ON mst.savings_account_id = ms.id WHERE sc.mobile_no IS NOT NULL AND (mo.id = ${officeId} OR ${officeId} = -1) AND (sc.staff_id = ${loanOfficerId} OR ${loanOfficerId} = -1) AND mst.savingsTransactionId = ${savingsTransactionId}"/> + + + - - - - - + + + + + - - - + value="SELECT sc.savingsId AS savingsId, sc.id AS clientId, sc.firstname, IFNULL(sc.middlename,'') AS middlename, sc.lastname, sc.display_name AS FullName, sc.mobile_no AS mobileNo, ms.`account_no` AS savingsAccountNo, ROUND(mst.amountPaid, ms.currency_digits) AS withdrawAmount, ms.account_balance_derived AS balance, mst.transactionDate AS transactionDate FROM m_office mo JOIN m_office ounder ON ounder.hierarchy LIKE CONCAT(mo.hierarchy, '%') AND ounder.hierarchy LIKE CONCAT('.', '%') LEFT JOIN ( SELECT sa.id AS savingsId, mc.id AS id, mc.firstname AS firstname, mc.middlename AS middlename, mc.lastname AS lastname, mc.display_name AS display_name, mc.status_enum AS status_enum, mc.mobile_no AS mobile_no, mc.office_id AS office_id, mc.staff_id AS staff_id FROM m_savings_account sa LEFT JOIN m_client mc ON mc.id = sa.client_id ORDER BY savingsId) sc ON sc.office_id = ounder.id RIGHT JOIN m_savings_account AS ms ON sc.savingsId = ms.id RIGHT JOIN( SELECT st.amount AS amountPaid, st.id, st.savings_account_id, st.id AS savingsTransactionId, st.transaction_date AS transactionDate FROM m_savings_account_transaction st WHERE st.is_reversed = 0 GROUP BY st.savings_account_id ) AS mst ON mst.savings_account_id = ms.id WHERE sc.mobile_no IS NOT NULL AND (mo.id = ${officeId} OR ${officeId} = -1) AND (sc.staff_id = ${loanOfficerId} OR ${loanOfficerId} = -1) AND mst.savingsTransactionId = ${savingsTransactionId}"/> + + + - - + + - - - - + + + + - + - - + + - - - - - - - - - - - - - - - - + + + + - - - - + + + + - - + + - - - - + + + + - - + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - + - - + + - + - - + + - + - - + + - - - - - - - + - - + + - + - - + + - - - - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - - - + + + + - - + + + + + + + + + + + + + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + + + + + + + + + + + + + + + + + + + - - + + - + - - - - + + + + - - + + - + - - - - + + + + - - - - + + + + - - - - + + + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + + - + - - + + - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - + - - + + - + - - + + - + - - - - + + + + - - - - + + + + - - + + - - - - - - - + - - + + - - - - - - - + - - + + - - - - - - - + - - + + - + - - - - + + + + - - + + - - - - - - - + - - - - + + + + - - - - + + + + - - + + - - - - - - - + - - + + - - - - - - - + - - + + - - - - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - + - - - - + + + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - + - - - - + + + + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - + - - + + - + - - + + - + - - + + - + - - + + - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - + + + + + + + + - + - - + + - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + @@ -16133,96 +14379,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0003_postgresql_specific_initial_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0003_postgresql_specific_initial_data.xml index 96e19ebe8c6..f9b257816eb 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0003_postgresql_specific_initial_data.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0003_postgresql_specific_initial_data.xml @@ -23,6 +23,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> + ANY @@ -474,6 +475,7 @@ + ANY @@ -642,42 +644,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -798,30 +764,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -834,18 +776,6 @@ - - - - - - - - - - - - @@ -855,7 +785,7 @@ - + @@ -867,7 +797,7 @@ - + @@ -879,7 +809,7 @@ - + @@ -891,7 +821,7 @@ - + @@ -903,7 +833,7 @@ - + @@ -915,7 +845,7 @@ - + @@ -927,7 +857,7 @@ - + @@ -939,7 +869,7 @@ - + @@ -951,7 +881,7 @@ - + @@ -963,7 +893,7 @@ - + @@ -975,7 +905,7 @@ - + @@ -987,7 +917,7 @@ - + @@ -999,7 +929,7 @@ - + @@ -1011,367 +941,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1383,7 +953,7 @@ - + @@ -1395,7 +965,7 @@ - + @@ -1407,7 +977,7 @@ - + @@ -1419,7 +989,7 @@ - + @@ -1431,7 +1001,7 @@ - + @@ -1443,7 +1013,7 @@ - + @@ -1455,7 +1025,7 @@ - + @@ -1467,7 +1037,7 @@ - + @@ -1479,103 +1049,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1755,7 +1229,7 @@ - + @@ -1767,7 +1241,7 @@ - + @@ -1779,7 +1253,7 @@ - + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0212_add_force_password_reset_config.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0212_add_force_password_reset_config.xml new file mode 100644 index 00000000000..16354740ef1 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0212_add_force_password_reset_config.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0213_transaction_summary_adding_originators.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0213_transaction_summary_adding_originators.xml new file mode 100644 index 00000000000..30db1335a2f --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0213_transaction_summary_adding_originators.xml @@ -0,0 +1,2649 @@ + + + + + + = '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39) + AND (t.office_id = ${officeId})), + slt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + rlt_except_charge_adj_and_accrual AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND t.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = t.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND + e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39) + AND (t.office_id = ${officeId})), + rlt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + slt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.is_reversed = false + AND t.transaction_type_enum IN (36, 39) + AND (t.office_id = ${officeId})), + rlt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.is_reversed = true + AND t.transaction_type_enum IN (36, 39) + AND (t.office_id = ${officeId})), + active_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + t.owner_id, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}'), + buyback_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + t.owner_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('BUYBACK', 'BUYBACK_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}') +SELECT '${endDate}' AS TransactionDate, + a.product AS Product, + CASE + WHEN a.transaction_type = 9999 THEN 'Asset Transfer' + WHEN a.transaction_type = 99999 THEN 'Asset Buyback' + ELSE (SELECT enum_message_property + FROM r_enum_value + WHERE enum_name = 'transaction_type_enum' + AND enum_id = a.transaction_type) END AS TransactionType_Name, + COALESCE((SELECT value FROM m_payment_type WHERE id = a.payment_type_id), + a.classification_name) AS PaymentType_Name, + a.chargetype AS chargetype, + a.reversal_indicator AS Reversed, + a.Allocation_Type AS Allocation_Type, + (SELECT code_value FROM m_code_value WHERE id = a.charge_off_reason_id) AS Chargeoff_ReasonCode, + CASE + WHEN a.transaction_type = 9999 THEN sum(a.amount) * + 1 + WHEN a.transaction_type = 99999 THEN sum(a.amount) * - 1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39) AND + a.reversal_indicator = false THEN sum(a.amount) * -1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39) AND + a.reversal_indicator = true THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36) AND a.reversal_indicator = false THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36) AND a.reversal_indicator = true + THEN sum(a.amount) * -1 END AS Transaction_Amount, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.asset_owner_id) AS Asset_owner_id, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.from_asset_owner_id) AS From_asset_owner_id, + a.originator_external_ids +FROM (SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum in (10, 34) + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived * -1 AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0) a +GROUP BY a.transactiondate, a.product, a.transaction_type, a.payment_type_id, a.classification_name, a.chargetype, + a.reversal_indicator, a.Allocation_Type, a.asset_owner_id, a.charge_off_reason_id, a.from_asset_owner_id, + a.originator_external_ids +ORDER BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + + ]]> + + report_name='Transaction Summary Report with Asset Owner' + + + + + = '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39) + AND (t.office_id = ${officeId})), + slt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + rlt_except_charge_adj_and_accrual AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND t.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = t.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND + e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39) + AND (t.office_id = ${officeId})), + rlt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + slt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.is_reversed = false + AND t.transaction_type_enum IN (36, 39) + AND (t.office_id = ${officeId})), + rlt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.is_reversed = true + AND t.transaction_type_enum IN (36, 39) + AND (t.office_id = ${officeId})), + active_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + t.owner_id, + t.previous_owner_id, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}'), + buyback_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + t.owner_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('BUYBACK', 'BUYBACK_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}') +SELECT '${endDate}' AS TransactionDate, + a.product AS Product, + CASE + WHEN a.transaction_type = 9999 THEN 'Asset Transfer' + WHEN a.transaction_type = 99999 THEN 'Asset Buyback' + ELSE (SELECT enum_message_property + FROM r_enum_value + WHERE enum_name = 'transaction_type_enum' + AND enum_id = a.transaction_type) END AS TransactionType_Name, + COALESCE((SELECT value FROM m_payment_type WHERE id = a.payment_type_id), + a.classification_name) AS PaymentType_Name, + a.chargetype AS chargetype, + a.reversal_indicator AS Reversed, + a.Allocation_Type AS Allocation_Type, + (SELECT code_value FROM m_code_value WHERE id = a.charge_off_reason_id) AS Chargeoff_ReasonCode, + CASE + WHEN a.transaction_type = 9999 THEN sum(a.amount) * + 1 + WHEN a.transaction_type = 99999 THEN sum(a.amount) * - 1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39) AND + a.reversal_indicator = false THEN sum(a.amount) * -1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39) AND + a.reversal_indicator = true THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36) AND a.reversal_indicator = false THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36) AND a.reversal_indicator = true + THEN sum(a.amount) * -1 END AS Transaction_Amount, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.asset_owner_id) AS Asset_owner_id, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.from_asset_owner_id) AS From_asset_owner_id, + a.originator_external_ids AS Originator_External_Ids +FROM (SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum in (10, 34) + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived * -1 AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0) a +GROUP BY a.transactiondate, a.product, a.transaction_type, a.payment_type_id, a.classification_name, a.chargetype, + a.reversal_indicator, a.Allocation_Type, a.asset_owner_id, a.charge_off_reason_id, a.from_asset_owner_id, + a.originator_external_ids +ORDER BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + + ]]> + + report_name='Transaction Summary Report with Asset Owner' + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0214_trial_balance_summary_adding_originators.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0214_trial_balance_summary_adding_originators.xml new file mode 100644 index 00000000000..10507229f0b --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0214_trial_balance_summary_adding_originators.xml @@ -0,0 +1,439 @@ + + + + + + (SELECT latest FROM aggregated_date) + ) + AND acc_gl_journal_entry.submitted_on_date < '${endDate}' + AND (acc_gl_journal_entry.office_id = ${officeId}) + GROUP BY productname, glcode, glname, assetowner, originators), + merged_historical_data AS (SELECT COALESCE(s.productname, p.productname) AS productname, + COALESCE(s.glcode, p.glcode) AS glcode, + COALESCE(s.glname, p.glname) AS glname, + COALESCE(s.assetowner, p.assetowner, 0) AS assetowner, + COALESCE(s.debitamount, 0) + COALESCE(p.debitamount, 0) AS debitamount, + COALESCE(s.creditamount, 0) + COALESCE(p.creditamount, 0) AS creditamount, + p.originators AS originators + FROM summary_snapshot_baseline_data s + LEFT JOIN post_snapshot_delta_data p + ON s.glcode = p.glcode + AND s.productname = p.productname + AND s.assetowner = p.assetowner + + UNION ALL + + SELECT p.productname AS productname, + p.glcode AS glcode, + p.glname AS glname, + COALESCE(p.assetowner, 0) AS assetowner, + COALESCE(p.debitamount, 0) AS debitamount, + COALESCE(p.creditamount, 0) AS creditamount, + p.originators AS originators + FROM post_snapshot_delta_data p + LEFT JOIN summary_snapshot_baseline_data s + ON s.glcode = p.glcode + AND s.productname = p.productname + AND s.assetowner = p.assetowner + WHERE s.glcode IS NULL), + current_cob_data AS (SELECT lp.name AS productname, + account_id, + acc_gl_account.gl_code AS glcode, + acc_gl_account.name AS glname, + CASE WHEN aw.owner_id IS NULL THEN 0 ELSE aw.owner_id END AS assetowner, + SUM(CASE WHEN acc_gl_journal_entry.type_enum = 2 THEN amount ELSE 0 END) AS debitamount, + SUM(CASE WHEN acc_gl_journal_entry.type_enum = 1 THEN amount ELSE 0 END) AS creditamount, + lo.originator_external_ids AS originators + FROM acc_gl_journal_entry + JOIN acc_gl_account ON acc_gl_account.id = acc_gl_journal_entry.account_id + JOIN m_loan m ON m.id = acc_gl_journal_entry.entity_id + JOIN m_product_loan lp ON lp.id = m.product_id + LEFT JOIN m_external_asset_owner_journal_entry_mapping aw + ON aw.journal_entry_id = acc_gl_journal_entry.id + LEFT JOIN loan_originators lo ON lo.loan_id = m.id + WHERE acc_gl_journal_entry.entity_type_enum = 1 + AND acc_gl_journal_entry.manual_entry = FALSE + AND acc_gl_journal_entry.submitted_on_date = '${endDate}' + AND (acc_gl_journal_entry.office_id = ${officeId}) + GROUP BY productname, account_id, glcode, glname, assetowner, originators) + +SELECT * +FROM (SELECT * + FROM retained_earning + WHERE glacct = (SELECT gl_code FROM acc_gl_account WHERE name = 'Retained Earnings Prior Year') + + UNION + + SELECT txnreport.postingdate, + txnreport.product, + txnreport.glacct, + txnreport.description, + txnreport.assetowner, + (COALESCE(txnreport.beginningbalance, 0) + COALESCE(summary.beginningbalance, 0)) AS beginningbalance, + txnreport.debitmovement AS debitmovement, + txnreport.creditmovement AS creditmovement, + (COALESCE(txnreport.endingbalance, 0) + COALESCE(summary.beginningbalance, 0)) AS endingbalance, + txnreport.originators AS originators + FROM (SELECT * + FROM (SELECT DISTINCT '${endDate}' AS postingdate, + loan.pname AS product, + loan.gl_code AS glacct, + loan.glname AS description, + COALESCE((SELECT external_id FROM m_external_asset_owner WHERE id = loan.assetowner), + 'self') AS assetowner, + loan.openingbalance AS beginningbalance, + (loan.debitamount * 1) AS debitmovement, + (loan.creditamount * -1) AS creditmovement, + (loan.openingbalance + loan.debitamount - loan.creditamount) AS endingbalance, + loan.originators AS originators + FROM (SELECT DISTINCT g.pname AS pname, + g.gl_code AS gl_code, + g.glname AS glname, + COALESCE(mh.assetowner, c.assetowner, 0) AS assetowner, + COALESCE(mh.debitamount, 0) - COALESCE(mh.creditamount, 0) AS openingbalance, + COALESCE(c.debitamount, 0) AS debitamount, + COALESCE(c.creditamount, 0) AS creditamount, + COALESCE(mh.originators, c.originators) AS originators + FROM (SELECT DISTINCT ag.gl_code, ag.id, pl.NAME AS pname, ag.NAME AS glname + FROM acc_gl_account ag + JOIN acc_product_mapping am ON am.gl_account_id = ag.id AND am.product_type = 1 + JOIN m_product_loan pl ON pl.id = am.product_id) g + LEFT JOIN merged_historical_data mh + ON g.gl_code = mh.glcode + AND mh.productname = g.pname + LEFT JOIN current_cob_data c + ON g.gl_code = c.glcode + AND c.productname = g.pname + AND mh.assetowner = c.assetowner + + UNION ALL + + SELECT DISTINCT c.productname AS pname, + c.glcode AS gl_code, + c.glname AS glname, + COALESCE(c.assetowner, 0) AS assetowner, + 0 AS openingbalance, + COALESCE(c.debitamount, 0) AS debitamount, + COALESCE(c.creditamount, 0) AS creditamount, + COALESCE(matched.originators, c.originators) AS originators + FROM current_cob_data c + LEFT JOIN (SELECT g3.gl_code, g3.pname, mh.assetowner, mh.originators + FROM (SELECT DISTINCT ag.gl_code, pl.NAME AS pname + FROM acc_gl_account ag + JOIN acc_product_mapping am + ON am.gl_account_id = ag.id AND am.product_type = 1 + JOIN m_product_loan pl ON pl.id = am.product_id) g3 + LEFT JOIN merged_historical_data mh + ON g3.gl_code = mh.glcode + AND mh.productname = g3.pname) matched + ON matched.gl_code = c.glcode + AND matched.pname = c.productname + AND matched.assetowner = c.assetowner + WHERE matched.gl_code IS NULL) loan) a) AS txnreport + LEFT JOIN retained_earning summary + ON txnreport.glacct = summary.glacct + AND txnreport.assetowner = summary.assetowner + AND summary.product = txnreport.product) report +WHERE report.endingbalance != 0 + OR report.debitmovement != 0 + OR report.creditmovement != 0 +ORDER BY glacct + + ]]> + + report_name='Trial Balance Summary Report with Asset Owner' + + + + + (SELECT latest FROM aggregated_date) + ) + AND acc_gl_journal_entry.submitted_on_date < '${endDate}' + AND (acc_gl_journal_entry.office_id = ${officeId}) + GROUP BY productname, glcode, glname, assetowner, originators), + merged_historical_data AS (SELECT COALESCE(s.productname, p.productname) AS productname, + COALESCE(s.glcode, p.glcode) AS glcode, + COALESCE(s.glname, p.glname) AS glname, + COALESCE(s.assetowner, p.assetowner, 0) AS assetowner, + COALESCE(s.debitamount, 0) + COALESCE(p.debitamount, 0) AS debitamount, + COALESCE(s.creditamount, 0) + COALESCE(p.creditamount, 0) AS creditamount, + p.originators AS originators + FROM summary_snapshot_baseline_data s + LEFT JOIN post_snapshot_delta_data p + ON s.glcode = p.glcode + AND s.productname = p.productname + AND s.assetowner = p.assetowner + + UNION ALL + + SELECT p.productname AS productname, + p.glcode AS glcode, + p.glname AS glname, + COALESCE(p.assetowner, 0) AS assetowner, + COALESCE(p.debitamount, 0) AS debitamount, + COALESCE(p.creditamount, 0) AS creditamount, + p.originators AS originators + FROM post_snapshot_delta_data p + LEFT JOIN summary_snapshot_baseline_data s + ON s.glcode = p.glcode + AND s.productname = p.productname + AND s.assetowner = p.assetowner + WHERE s.glcode IS NULL), + current_cob_data AS (SELECT lp.name AS productname, + account_id, + acc_gl_account.gl_code AS glcode, + acc_gl_account.name AS glname, + CASE WHEN aw.owner_id IS NULL THEN 0 ELSE aw.owner_id END AS assetowner, + SUM(CASE WHEN acc_gl_journal_entry.type_enum = 2 THEN amount ELSE 0 END) AS debitamount, + SUM(CASE WHEN acc_gl_journal_entry.type_enum = 1 THEN amount ELSE 0 END) AS creditamount, + lo.originator_external_ids AS originators + FROM acc_gl_journal_entry + JOIN acc_gl_account ON acc_gl_account.id = acc_gl_journal_entry.account_id + JOIN m_loan m ON m.id = acc_gl_journal_entry.entity_id + JOIN m_product_loan lp ON lp.id = m.product_id + LEFT JOIN m_external_asset_owner_journal_entry_mapping aw + ON aw.journal_entry_id = acc_gl_journal_entry.id + LEFT JOIN loan_originators lo ON lo.loan_id = m.id + WHERE acc_gl_journal_entry.entity_type_enum = 1 + AND acc_gl_journal_entry.manual_entry = FALSE + AND acc_gl_journal_entry.submitted_on_date = '${endDate}' + AND (acc_gl_journal_entry.office_id = ${officeId}) + GROUP BY productname, account_id, glcode, glname, assetowner, originators) + +SELECT * +FROM (SELECT * + FROM retained_earning + WHERE glacct = (SELECT gl_code FROM acc_gl_account WHERE name = 'Retained Earnings Prior Year') + + UNION + + SELECT txnreport.postingdate, + txnreport.product, + txnreport.glacct, + txnreport.description, + txnreport.assetowner, + (COALESCE(txnreport.beginningbalance, 0) + COALESCE(summary.beginningbalance, 0)) AS beginningbalance, + txnreport.debitmovement AS debitmovement, + txnreport.creditmovement AS creditmovement, + (COALESCE(txnreport.endingbalance, 0) + COALESCE(summary.beginningbalance, 0)) AS endingbalance, + txnreport.originators AS originators + FROM (SELECT * + FROM (SELECT DISTINCT '${endDate}' AS postingdate, + loan.pname AS product, + loan.gl_code AS glacct, + loan.glname AS description, + COALESCE((SELECT external_id FROM m_external_asset_owner WHERE id = loan.assetowner), + 'self') AS assetowner, + loan.openingbalance AS beginningbalance, + (loan.debitamount * 1) AS debitmovement, + (loan.creditamount * -1) AS creditmovement, + (loan.openingbalance + loan.debitamount - loan.creditamount) AS endingbalance, + loan.originators AS originators + FROM (SELECT DISTINCT g.pname AS pname, + g.gl_code AS gl_code, + g.glname AS glname, + COALESCE(mh.assetowner, c.assetowner, 0) AS assetowner, + COALESCE(mh.debitamount, 0) - COALESCE(mh.creditamount, 0) AS openingbalance, + COALESCE(c.debitamount, 0) AS debitamount, + COALESCE(c.creditamount, 0) AS creditamount, + COALESCE(mh.originators, c.originators) AS originators + FROM (SELECT DISTINCT ag.gl_code, ag.id, pl.NAME AS pname, ag.NAME AS glname + FROM acc_gl_account ag + JOIN acc_product_mapping am ON am.gl_account_id = ag.id AND am.product_type = 1 + JOIN m_product_loan pl ON pl.id = am.product_id) g + LEFT JOIN merged_historical_data mh + ON g.gl_code = mh.glcode + AND mh.productname = g.pname + LEFT JOIN current_cob_data c + ON g.gl_code = c.glcode + AND c.productname = g.pname + AND mh.assetowner = c.assetowner + + UNION ALL + + SELECT DISTINCT c.productname AS pname, + c.glcode AS gl_code, + c.glname AS glname, + COALESCE(c.assetowner, 0) AS assetowner, + 0 AS openingbalance, + COALESCE(c.debitamount, 0) AS debitamount, + COALESCE(c.creditamount, 0) AS creditamount, + COALESCE(matched.originators, c.originators) AS originators + FROM current_cob_data c + LEFT JOIN (SELECT g3.gl_code, g3.pname, mh.assetowner, mh.originators + FROM (SELECT DISTINCT ag.gl_code, pl.NAME AS pname + FROM acc_gl_account ag + JOIN acc_product_mapping am + ON am.gl_account_id = ag.id AND am.product_type = 1 + JOIN m_product_loan pl ON pl.id = am.product_id) g3 + LEFT JOIN merged_historical_data mh + ON g3.gl_code = mh.glcode + AND mh.productname = g3.pname) matched + ON matched.gl_code = c.glcode + AND matched.pname = c.productname + AND matched.assetowner = c.assetowner + WHERE matched.gl_code IS NULL) loan) a) AS txnreport + LEFT JOIN retained_earning summary + ON txnreport.glacct = summary.glacct + AND txnreport.assetowner = summary.assetowner + AND summary.product = txnreport.product) report +WHERE report.endingbalance != 0 + OR report.debitmovement != 0 + OR report.creditmovement != 0 +ORDER BY glacct + + ]]> + + report_name='Trial Balance Summary Report with Asset Owner' + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0215_transaction_summary_reports_add_buydown_fee_types.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0215_transaction_summary_reports_add_buydown_fee_types.xml new file mode 100644 index 00000000000..ac81793cc1c --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0215_transaction_summary_reports_add_buydown_fee_types.xml @@ -0,0 +1,3263 @@ + + + + + + = '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39, 42, 43) + AND (t.office_id = ${officeId})), + slt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + rlt_except_charge_adj_and_accrual AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND t.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = t.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND + e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39, 42, 43) + AND (t.office_id = ${officeId})), + rlt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + slt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.is_reversed = false + AND t.transaction_type_enum IN (36, 39, 42, 43) + AND (t.office_id = ${officeId})), + rlt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.is_reversed = true + AND t.transaction_type_enum IN (36, 39, 42, 43) + AND (t.office_id = ${officeId})), + active_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + t.owner_id, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}'), + buyback_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + t.owner_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('BUYBACK', 'BUYBACK_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}') +SELECT '${endDate}' AS TransactionDate, + a.product AS Product, + CASE + WHEN a.transaction_type = 9999 THEN 'Asset Transfer' + WHEN a.transaction_type = 99999 THEN 'Asset Buyback' + ELSE (SELECT enum_message_property + FROM r_enum_value + WHERE enum_name = 'transaction_type_enum' + AND enum_id = a.transaction_type) END AS TransactionType_Name, + COALESCE((SELECT value FROM m_payment_type WHERE id = a.payment_type_id), + a.classification_name) AS PaymentType_Name, + a.chargetype AS chargetype, + a.reversal_indicator AS Reversed, + a.Allocation_Type AS Allocation_Type, + (SELECT code_value FROM m_code_value WHERE id = a.charge_off_reason_id) AS Chargeoff_ReasonCode, + CASE + WHEN a.transaction_type = 9999 THEN sum(a.amount) * + 1 + WHEN a.transaction_type = 99999 THEN sum(a.amount) * - 1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39, 41, 43) AND + a.reversal_indicator = false THEN sum(a.amount) * -1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39, 41, 43) AND + a.reversal_indicator = true THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36, 40, 42) AND a.reversal_indicator = false THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36, 40, 42) AND a.reversal_indicator = true + THEN sum(a.amount) * -1 END AS Transaction_Amount, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.asset_owner_id) AS Asset_owner_id, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.from_asset_owner_id) AS From_asset_owner_id, + a.originator_external_ids +FROM (SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1, 40, 41) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum in (10, 34) + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1, 40, 41) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null::bigint AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived * -1 AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0) a +GROUP BY a.transactiondate, a.product, a.transaction_type, a.payment_type_id, a.classification_name, a.chargetype, + a.reversal_indicator, a.Allocation_Type, a.asset_owner_id, a.charge_off_reason_id, a.from_asset_owner_id, + a.originator_external_ids +ORDER BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + + ]]> + + report_name='Transaction Summary Report with Asset Owner' + + + + + = '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39, 42, 43) + AND (t.office_id = ${officeId})), + slt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + rlt_except_charge_adj_and_accrual AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND t.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = t.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND + e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum not in (10, 26, 32, 34, 36, 39, 42, 43) + AND (t.office_id = ${officeId})), + rlt_charge_adj AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 26 + AND (t.office_id = ${officeId})), + slt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.is_reversed = false + AND t.transaction_type_enum IN (36, 39, 42, 43) + AND (t.office_id = ${officeId})), + rlt_cap_income_amortization AS (SELECT '${endDate}' AS transactiondate, + t.id, + l.name, + t.transaction_type_enum, + d.payment_type_id, + CASE + WHEN d.payment_type_id IS NULL AND bt.classification_cv_id IS NOT NULL + THEN (SELECT code_value FROM m_code_value WHERE id = bt.classification_cv_id) + ELSE NULL END AS classification_name, + t.overpayment_portion_derived, + t.principal_portion_derived, + t.interest_portion_derived, + t.fee_charges_portion_derived, + t.penalty_charges_portion_derived, + t.amount, + e.status, + e.settlement_date, + e.owner_id, + m.charged_off_on_date, + t.transaction_date, + m.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_payment_detail d ON d.id = t.payment_detail_id + JOIN m_loan_amortization_allocation_mapping map + ON map.amortization_loan_transaction_id = t.id + JOIN m_loan_transaction bt ON bt.id = map.base_loan_transaction_id + LEFT JOIN m_external_asset_owner_transfer e ON e.loan_id = t.loan_id AND + e.settlement_date < + '${endDate}' AND + e.effective_date_to >= + '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.is_reversed = true + AND t.transaction_type_enum IN (36, 39, 42, 43) + AND (t.office_id = ${officeId})), + active_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + t.owner_id, + t.previous_owner_id, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}'), + buyback_external_asset_owner_transfers AS (SELECT '${endDate}' AS transactiondate, + t.id, + p.name, + dt.principal_outstanding_derived, + dt.interest_outstanding_derived, + dt.fee_charges_outstanding_derived, + dt.penalty_charges_outstanding_derived, + dt.total_overpaid_derived, + l.charged_off_on_date, + t.settlement_date, + l.charge_off_reason_cv_id, + t.owner_id, + lo.originator_external_ids + FROM m_external_asset_owner_transfer t + JOIN m_loan l ON l.id = t.loan_id + JOIN m_client c ON c.id = l.client_id + JOIN m_product_loan p ON p.id = l.product_id + JOIN m_external_asset_owner_transfer_details dt + ON dt.asset_owner_transfer_id = t.id + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.status in ('BUYBACK', 'BUYBACK_INTERMEDIATE') + AND c.office_id = ${officeId} + AND t.settlement_date = '${endDate}') +SELECT '${endDate}' AS TransactionDate, + a.product AS Product, + CASE + WHEN a.transaction_type = 9999 THEN 'Asset Transfer' + WHEN a.transaction_type = 99999 THEN 'Asset Buyback' + ELSE (SELECT enum_message_property + FROM r_enum_value + WHERE enum_name = 'transaction_type_enum' + AND enum_id = a.transaction_type) END AS TransactionType_Name, + COALESCE((SELECT value FROM m_payment_type WHERE id = a.payment_type_id), + a.classification_name) AS PaymentType_Name, + a.chargetype AS chargetype, + a.reversal_indicator AS Reversed, + a.Allocation_Type AS Allocation_Type, + (SELECT code_value FROM m_code_value WHERE id = a.charge_off_reason_id) AS Chargeoff_ReasonCode, + CASE + WHEN a.transaction_type = 9999 THEN sum(a.amount) * + 1 + WHEN a.transaction_type = 99999 THEN sum(a.amount) * - 1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39, 41, 43) AND + a.reversal_indicator = false THEN sum(a.amount) * -1 + WHEN a.transaction_type IN (2, 23, 21, 22, 24, 4, 5, 8, 6, 27, 9, 26, 28, 31, 33, 34, 37, 39, 41, 43) AND + a.reversal_indicator = true THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36, 40, 42) AND a.reversal_indicator = false THEN sum(a.amount) * + 1 + WHEN a.transaction_type IN (1, 10, 25, 20, 35, 36, 40, 42) AND a.reversal_indicator = true + THEN sum(a.amount) * -1 END AS Transaction_Amount, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.asset_owner_id) AS Asset_owner_id, + (SELECT external_id + FROM m_external_asset_owner + WHERE id = a.from_asset_owner_id) AS From_asset_owner_id, + a.originator_external_ids AS Originator_External_Ids +FROM (SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1, 40, 41) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM slt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.transaction_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + t.originator_external_ids AS from_asset_owner_id + FROM rlt_cap_income_amortization AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids AS from_asset_owner_id + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.submitted_on_date = '${endDate}' + AND t.transaction_type_enum in (10, 34) + AND t.is_reversed = false + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM slt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE + WHEN t.transaction_type_enum in (1, 40, 41) THEN (CASE + WHEN t.amount is null THEN 0 + WHEN t.overpayment_portion_derived is null THEN t.amount + WHEN t.overpayment_portion_derived is not null + THEN t.amount - t.overpayment_portion_derived + ELSE t.amount END) + ELSE (CASE + WHEN t.principal_portion_derived is null THEN 0 + ELSE t.principal_portion_derived end) END amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + t.classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_except_charge_adj_and_accrual AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = false + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + mc.name AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE WHEN pd.amount is null THEN 0 ELSE pd.amount END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + JOIN m_loan_charge_paid_by pd ON pd.loan_transaction_id = t.id + JOIN m_loan_charge c ON c.id = pd.loan_charge_id + JOIN m_charge mc ON mc.id = c.charge_id AND mc.is_penalty = true + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + l.name AS product, + t.transaction_type_enum AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN e.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND e.settlement_date < '${endDate}' + THEN e.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (m.charged_off_on_date <= t.transaction_date) + THEN m.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + lo.originator_external_ids + FROM m_loan_transaction t + JOIN m_loan m ON m.id = t.loan_id + JOIN m_product_loan l ON l.id = m.product_id + LEFT JOIN m_external_asset_owner_transfer e + ON e.loan_id = t.loan_id AND e.settlement_date < '${endDate}' AND + e.effective_date_to >= '${endDate}' + LEFT JOIN loan_originators lo ON lo.loan_id = t.loan_id + WHERE t.reversed_on_date = '${endDate}' + AND t.transaction_type_enum = 10 + AND t.is_reversed = true + AND (t.office_id = ${officeId}) + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Fees' AS Allocation_Type, + CASE WHEN t.fee_charges_portion_derived is null THEN 0 ELSE t.fee_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Penalty' AS Allocation_Type, + CASE + WHEN t.penalty_charges_portion_derived is null THEN 0 + ELSE t.penalty_charges_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Interest' AS Allocation_Type, + CASE WHEN t.interest_portion_derived is null THEN 0 ELSE t.interest_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Principal' AS Allocation_Type, + CASE WHEN t.principal_portion_derived is null THEN 0 ELSE t.principal_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + t.transaction_type_enum AS transaction_type, + t.payment_type_id, + null AS classification_name, + '' AS chargetype, + true AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_Type, + CASE WHEN t.overpayment_portion_derived is null THEN 0 ELSE t.overpayment_portion_derived END AS amount, + CASE + WHEN t.status in ('ACTIVE', 'ACTIVE_INTERMEDIATE') AND t.settlement_date < '${endDate}' + THEN t.owner_id END AS asset_owner_id, + CASE + WHEN t.transaction_type_enum = 27 OR (t.charged_off_on_date <= t.transaction_date) + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + null AS from_asset_owner_id, + t.originator_external_ids + FROM rlt_charge_adj AS t + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT t.transactiondate, + t.id, + t.name AS product, + 9999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived AS amount, + t.owner_id AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.previous_owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM active_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Principal' AS Allocation_type, + t.principal_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.principal_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Interest' AS Allocation_type, + t.interest_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.interest_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Fees' AS Allocation_type, + t.fee_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.fee_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Penalty' AS Allocation_type, + t.penalty_charges_outstanding_derived AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.penalty_charges_outstanding_derived > 0 + UNION ALL + SELECT '${endDate}' AS transactiondate, + t.id, + t.name AS product, + 99999 AS transaction_type, + null AS payment_type_id, + null AS classification_name, + '' AS chargetype, + false AS reversal_indicator, + 'Unallocated Credit (UNC)' AS Allocation_type, + t.total_overpaid_derived * -1 AS amount, + null AS asset_owner_id, + CASE + WHEN t.charged_off_on_date <= t.settlement_date + THEN t.charge_off_reason_cv_id END AS charge_off_reason_id, + t.owner_id AS from_asset_owner_id, + t.originator_external_ids + FROM buyback_external_asset_owner_transfers AS t + WHERE t.total_overpaid_derived > 0) a +GROUP BY a.transactiondate, a.product, a.transaction_type, a.payment_type_id, a.classification_name, a.chargetype, + a.reversal_indicator, a.Allocation_Type, a.asset_owner_id, a.charge_off_reason_id, a.from_asset_owner_id, + a.originator_external_ids +ORDER BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + + ]]> + + report_name='Transaction Summary Report with Asset Owner' + + + + + + + + report_name='Transaction Summary Report' + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml new file mode 100644 index 00000000000..50f7a573378 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0217_force_withdrawal_configs.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0217_force_withdrawal_configs.xml new file mode 100644 index 00000000000..575ca65cb81 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0217_force_withdrawal_configs.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO m_role_permission (role_id, permission_id) + SELECT 1, p.id FROM m_permission p + WHERE p.code IN ('FORCE_WITHDRAWAL_SAVINGSACCOUNT', 'FORCE_WITHDRAWAL_SAVINGSACCOUNT_CHECKER') + AND NOT EXISTS (SELECT 1 FROM m_role_permission rp WHERE rp.role_id = 1 AND rp.permission_id = p.id); + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_initial_schema.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml similarity index 86% rename from fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_initial_schema.xml rename to fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml index b85a3489f4e..9b995bc8639 100644 --- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_initial_schema.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml @@ -21,6 +21,8 @@ --> - + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> + + ALTER DATABASE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + diff --git a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml index 73c369c061e..9fe5069589f 100644 --- a/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml +++ b/fineract-provider/src/main/resources/jpa/static-weaving/module/fineract-provider/persistence.xml @@ -218,6 +218,11 @@ org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain org.apache.fineract.portfolio.collateral.domain.LoanCollateral + + org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct + org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductPaymentAllocationRule + org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductConfigurableAttributes + org.apache.fineract.portfolio.loanproduct.domain.AllocationTypeListConverter org.apache.fineract.portfolio.loanaccount.domain.AccountingRuleTypeConverter @@ -227,6 +232,9 @@ org.apache.fineract.portfolio.loanproduct.domain.SupportedInterestRefundTypesListConverter org.apache.fineract.portfolio.loanaccount.domain.LoanStatusConverter + + org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalPaymentAllocationTypeListConverter + org.apache.fineract.portfolio.tax.domain.TaxComponent org.apache.fineract.portfolio.tax.domain.TaxComponentHistory @@ -272,6 +280,7 @@ org.apache.fineract.portfolio.account.domain.AccountTransferTransaction + org.apache.fineract.notification.domain.NotificationMapper org.apache.fineract.notification.domain.Notification @@ -279,11 +288,7 @@ org.apache.fineract.adhocquery.domain.AdHoc - - org.apache.fineract.mix.domain.MixTaxonomyMapping - - org.apache.fineract.cob.domain.LoanAccountLock org.apache.fineract.cob.domain.BatchBusinessStep diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java index 0048699748e..16b3c2487e6 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java @@ -40,6 +40,7 @@ import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy; import org.apache.fineract.batch.command.internal.CreateDatatableEntryCommandStrategy; import org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy; +import org.apache.fineract.batch.command.internal.CreateSavingsAccountChargeCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionByLoanExternalIdCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy; @@ -91,6 +92,8 @@ private static Stream provideCommandStrategies() { Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1?associations=all&exclude=guarantors", HttpMethod.GET, "getLoanByExternalIdCommandStrategy", mock(GetLoanByExternalIdCommandStrategy.class)), Arguments.of("savingsaccounts", HttpMethod.POST, "applySavingsCommandStrategy", mock(ApplySavingsCommandStrategy.class)), + Arguments.of("savingsaccounts/123/charges", HttpMethod.POST, "createSavingsAccountChargeCommandStrategy", + mock(CreateSavingsAccountChargeCommandStrategy.class)), Arguments.of("loans/123/charges", HttpMethod.POST, "createChargeCommandStrategy", mock(CreateChargeCommandStrategy.class)), Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/charges", HttpMethod.POST, "createChargeByLoanExternalIdCommandStrategy", mock(CreateChargeByLoanExternalIdCommandStrategy.class)), diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java new file mode 100644 index 00000000000..a5469118d8e --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java @@ -0,0 +1,116 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.batch.command.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.UriInfo; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link CreateSavingsAccountChargeCommandStrategy}. + */ +public class CreateSavingsAccountChargeCommandStrategyTest { + + /** + * Test {@link CreateSavingsAccountChargeCommandStrategy#execute} happy path scenario. + */ + @Test + public void testExecuteSuccessScenario() { + final TestContext testContext = new TestContext(); + final Long savingsAccountId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final BatchRequest batchRequest = getBatchRequest(savingsAccountId); + final String responseBody = "myResponseBody"; + + when(testContext.savingsAccountChargesApiResource.addSavingsAccountCharge(savingsAccountId, batchRequest.getBody())) + .thenReturn(responseBody); + + BatchResponse batchResponse = testContext.subjectToTest.execute(batchRequest, testContext.uriInfo); + + assertEquals(HttpStatus.SC_OK, batchResponse.getStatusCode()); + assertSame(responseBody, batchResponse.getBody()); + assertEquals(batchRequest.getRequestId(), batchResponse.getRequestId()); + assertEquals(batchRequest.getHeaders(), batchResponse.getHeaders()); + + verify(testContext.savingsAccountChargesApiResource).addSavingsAccountCharge(savingsAccountId, batchRequest.getBody()); + } + + /** + * Creates and returns a request with the given savings account id. + * + * @param savingsAccountId + * the savings account id + * @return BatchRequest + */ + private BatchRequest getBatchRequest(final Long savingsAccountId) { + + final BatchRequest br = new BatchRequest(); + String relativeUrl = "savingsaccounts/" + savingsAccountId + "/charges"; + + br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setRelativeUrl(relativeUrl); + br.setMethod(HttpMethod.POST); + br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setBody("{\"chargeId\":\"1\",\"amount\":\"100\"}"); + + return br; + } + + /** + * Private test context class used since testng runs in parallel to avoid state between tests + */ + private static class TestContext { + + /** + * Mock URI info. + */ + @Mock + private UriInfo uriInfo; + + /** + * Mock savings account charges API resource. + */ + @Mock + private SavingsAccountChargesApiResource savingsAccountChargesApiResource; + + /** + * The {@link CreateSavingsAccountChargeCommandStrategy} under test. + */ + private final CreateSavingsAccountChargeCommandStrategy subjectToTest; + + /** + * Constructor. + */ + TestContext() { + MockitoAnnotations.openMocks(this); + subjectToTest = new CreateSavingsAccountChargeCommandStrategy(savingsAccountChargesApiResource); + } + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java index ba559cbc8b9..b857cdb70dc 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java @@ -32,8 +32,8 @@ import java.util.List; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.LoanReadException; -import org.apache.fineract.cob.loan.LoanLockingService; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.exceptions.LockedReadException; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -45,7 +45,7 @@ public class LoanItemListenerStepDefinitions implements En { - private LoanLockingService loanLockingService = mock(LoanLockingService.class); + private LockingService loanLockingService = mock(LockingService.class); private TransactionTemplate transactionTemplate = spy(TransactionTemplate.class); private ChunkProcessingLoanItemListener loanItemListener = new ChunkProcessingLoanItemListener(loanLockingService, transactionTemplate); @@ -58,7 +58,7 @@ public class LoanItemListenerStepDefinitions implements En { public LoanItemListenerStepDefinitions() { Given("/^The LoanItemListener.onReadError method (.*)$/", (String action) -> { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); - exception = new LoanReadException(1L, new RuntimeException("fail")); + exception = new LockedReadException(1L, new RuntimeException("fail")); loanAccountLock = new LoanAccountLock(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); when(loanLockingService.findByLoanIdAndLockOwner(1L, LockOwner.LOAN_COB_CHUNK_PROCESSING)).thenReturn(loanAccountLock); transactionTemplate.setTransactionManager(mock(PlatformTransactionManager.class)); @@ -83,7 +83,7 @@ public LoanItemListenerStepDefinitions() { Given("/^The LoanItemListener.onProcessError method (.*)$/", (String action) -> { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); - exception = new LoanReadException(1L, new RuntimeException("fail")); + exception = new LockedReadException(1L, new RuntimeException("fail")); loanAccountLock = new LoanAccountLock(2L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); when(loanLockingService.findByLoanIdAndLockOwner(2L, LockOwner.LOAN_COB_CHUNK_PROCESSING)).thenReturn(loanAccountLock); when(loan.getId()).thenReturn(2L); @@ -108,7 +108,7 @@ public LoanItemListenerStepDefinitions() { Given("/^The LoanItemListener.onWriteError method (.*)$/", (String action) -> { ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); - exception = new LoanReadException(3L, new RuntimeException("fail")); + exception = new LockedReadException(3L, new RuntimeException("fail")); loanAccountLock = new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.now(ZoneId.systemDefault())); when(loanLockingService.findByLoanIdAndLockOwner(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING)).thenReturn(loanAccountLock); when(loan.getId()).thenReturn(3L); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java index a91f554c6a6..040339fd4f9 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java @@ -35,7 +35,9 @@ import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.LoanLockCannotBeAppliedException; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.exceptions.LockCannotBeAppliedException; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -55,14 +57,14 @@ public class ApplyLoanLockTaskletStepDefinitions implements En { ArgumentCaptor valueCaptor = ArgumentCaptor.forClass(List.class); ArgumentCaptor lockOwnerValueCaptor = ArgumentCaptor.forClass(LockOwner.class); - private LoanLockingService loanLockingService = mock(LoanLockingService.class); + private LockingService loanLockingService = mock(LockingService.class); private FineractProperties fineractProperties = mock(FineractProperties.class); private FineractProperties.FineractQueryProperties fineractQueryProperties = mock(FineractProperties.FineractQueryProperties.class); - private RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); + private RetrieveIdService retrieveIdService = mock(RetrieveIdService.class); private TransactionTemplate transactionTemplate = spy(TransactionTemplate.class); - private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(fineractProperties, loanLockingService, - retrieveLoanIdService, transactionTemplate); + private ApplyLoanLockTasklet applyLoanLockTasklet = new ApplyLoanLockTasklet(fineractProperties, loanLockingService, retrieveIdService, + transactionTemplate); private RepeatStatus resultItem; private StepContribution stepContribution; @@ -76,9 +78,8 @@ public ApplyLoanLockTaskletStepDefinitions() { StepExecution stepExecution = new StepExecution("test", jobExecution); ExecutionContext executionContext = new ExecutionContext(); COBParameter loanCOBParameter = new COBParameter(1L, 4L); - executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter); - lenient().when( - retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) + executionContext.put(LoanCOBConstant.COB_PARAMETER, loanCOBParameter); + lenient().when(retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) .thenReturn(List.of(1L, 2L, 3L, 4L)); stepExecution.setExecutionContext(executionContext); stepContribution = new StepContribution(stepExecution); @@ -142,7 +143,7 @@ public ApplyLoanLockTaskletStepDefinitions() { }); Then("throw LoanLockCannotBeAppliedException exception ApplyLoanLockTasklet.execute method", () -> { - assertThrows(LoanLockCannotBeAppliedException.class, () -> { + assertThrows(LockCannotBeAppliedException.class, () -> { resultItem = applyLoanLockTasklet.execute(stepContribution, null); }); }); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java index fdd90bb6cc0..a5c1459e91a 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerTest.java @@ -27,9 +27,11 @@ import java.util.Map; import java.util.Set; import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.COBConstant; import org.apache.fineract.cob.data.BusinessStepNameAndOrder; import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.data.COBPartition; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -53,7 +55,7 @@ class LoanCOBPartitionerTest { @Mock private COBBusinessStepService cobBusinessStepService; @Mock - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveIdService retrieveIdService; @Mock private JobOperator jobOperator; @Mock @@ -69,13 +71,13 @@ public void testLoanCOBPartitioner() { when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(5); when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME)) .thenReturn(BUSINESS_STEP_SET); - when(retrieveLoanIdService.retrieveLoanCOBPartitions(1L, BUSINESS_DATE, false, 5)) + when(retrieveIdService.retrieveLoanCOBPartitions(1L, BUSINESS_DATE, false, 5)) .thenReturn(List.of(new COBPartition(1L,10L, 1L, 5L), new COBPartition(11L,20L, 2L, 4L))); when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getExecutionContext()).thenReturn(executionContext); when(executionContext.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)).thenReturn(BUSINESS_DATE); when(executionContext.get(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME)).thenReturn(false); - LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator,stepExecution, 1L); + LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveIdService, jobOperator,stepExecution, 1L); //when Map partitions = loanCOBPartitioner.partition(1); @@ -95,7 +97,7 @@ public void testLoanCOBPartitionerEmptyBusinessSteps() throws NoSuchJobExecution when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getId()).thenReturn(123L); - LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator, stepExecution, 1L); + LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveIdService, jobOperator, stepExecution, 1L); //when Map partitions = loanCOBPartitioner.partition(1); @@ -111,13 +113,13 @@ public void testLoanCOBPartitionerNoLoansFound() { when(propertyService.getPartitionSize(LoanCOBConstant.JOB_NAME)).thenReturn(5); when(cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME)) .thenReturn(BUSINESS_STEP_SET); - when(retrieveLoanIdService.retrieveLoanCOBPartitions(1L, BUSINESS_DATE, false, 5)) + when(retrieveIdService.retrieveLoanCOBPartitions(1L, BUSINESS_DATE, false, 5)) .thenReturn(List.of()); when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getExecutionContext()).thenReturn(executionContext); when(executionContext.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)).thenReturn(BUSINESS_DATE); when(executionContext.get(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME)).thenReturn(false); - LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator,stepExecution, 1L); + LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveIdService, jobOperator,stepExecution, 1L); //when Map partitions = loanCOBPartitioner.partition(1); @@ -130,13 +132,13 @@ public void testLoanCOBPartitionerNoLoansFound() { private void validatePartitions(Map partitions, int index, long min, long max, String businessDate, String isCatchUp) { Assertions.assertEquals(BUSINESS_STEP_SET, - partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.BUSINESS_STEPS)); + partitions.get(COBConstant.PARTITION_PREFIX + index).get(LoanCOBConstant.BUSINESS_STEPS)); Assertions.assertEquals(new COBParameter(min, max), - partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.LOAN_COB_PARAMETER)); - Assertions.assertEquals("partition_" + index, partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get("partition")); + partitions.get(COBConstant.PARTITION_PREFIX + index).get(LoanCOBConstant.COB_PARAMETER)); + Assertions.assertEquals("partition_" + index, partitions.get(COBConstant.PARTITION_PREFIX + index).get("partition")); Assertions.assertEquals(businessDate, - partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)); + partitions.get(COBConstant.PARTITION_PREFIX + index).get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME)); Assertions.assertEquals(isCatchUp, - partitions.get(LoanCOBPartitioner.PARTITION_PREFIX + index).get(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME)); + partitions.get(COBConstant.PARTITION_PREFIX + index).get(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME)); } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java index ac4a858770b..b332495448b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java @@ -39,7 +39,10 @@ import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.cob.exceptions.LoanReadException; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.exceptions.LockedReadException; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -53,13 +56,14 @@ public class LoanItemReaderStepDefinitions implements En { private LoanRepository loanRepository = mock(LoanRepository.class); - private RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); + private RetrieveIdService retrieveIdService = mock(RetrieveIdService.class); private CustomJobParameterResolver customJobParameterResolver = mock(CustomJobParameterResolver.class); - private LoanLockingService lockingService = mock(LoanLockingService.class); + private LockingService lockingService = mock(LockingService.class); - private LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, lockingService); + private LoanItemReader loanItemReader = new LoanItemReader(loanRepository, + new BeforeStepLockingItemReaderHelper(retrieveIdService, lockingService)); private Loan loan = mock(Loan.class); @@ -82,11 +86,11 @@ public LoanItemReaderStepDefinitions() { maxLoanId = splitAccounts.get(splitAccounts.size() - 1); } COBParameter loanCOBParameter = new COBParameter(minLoanId, maxLoanId); - stepExecutionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, loanCOBParameter); + stepExecutionContext.put(LoanCOBConstant.COB_PARAMETER, loanCOBParameter); stepExecution.setExecutionContext(stepExecutionContext); lenient().when( - this.retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) + this.retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) .thenReturn(splitAccounts); HashMap businessDates = new HashMap<>(); @@ -124,7 +128,7 @@ public LoanItemReaderStepDefinitions() { }); Then("throw exception LoanItemReader.read method", () -> { - assertThrows(LoanReadException.class, () -> { + assertThrows(LockedReadException.class, () -> { resultItem = this.loanItemReader.read(); }); }); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java index 30dbc970781..33effa8a54e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderTest.java @@ -36,6 +36,9 @@ import org.apache.fineract.cob.data.COBParameter; import org.apache.fineract.cob.domain.LoanAccountLock; import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -58,10 +61,10 @@ class LoanItemReaderTest { private LoanRepository loanRepository; @Mock - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveIdService retrieveIdService; @Mock - private LoanLockingService loanLockingService; + private LockingService loanLockingService; @Mock private StepExecution stepExecution; @@ -83,13 +86,14 @@ public void tearDown() { public void testLoanItemReaderSimple() throws Exception { // given ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "test", "test", "UTC", null)); - LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, loanLockingService); + LoanItemReader loanItemReader = new LoanItemReader(loanRepository, + new BeforeStepLockingItemReaderHelper<>(retrieveIdService, loanLockingService)); when(stepExecution.getExecutionContext()).thenReturn(executionContext); when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getExecutionContext()).thenReturn(executionContext); COBParameter loanCOBParameter = new COBParameter(1L, 5L); - when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter); - when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) + when(executionContext.get(LoanCOBConstant.COB_PARAMETER)).thenReturn(loanCOBParameter); + when(retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) .thenReturn(new ArrayList<>(List.of(1L, 2L, 3L, 4L, 5L))); List accountLocks = Stream.of(1L, 2L, 3L, 4L, 5L) .map(l -> new LoanAccountLock(l, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.of(2023, 7, 25))).toList(); @@ -112,13 +116,14 @@ public void testLoanItemReaderSimple() throws Exception { public void testLoanItemReadNoOpenLoansFound() throws Exception { // given ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "test", "test", "UTC", null)); - LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, loanLockingService); + LoanItemReader loanItemReader = new LoanItemReader(loanRepository, + new BeforeStepLockingItemReaderHelper<>(retrieveIdService, loanLockingService)); when(stepExecution.getExecutionContext()).thenReturn(executionContext); when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getExecutionContext()).thenReturn(executionContext); COBParameter loanCOBParameter = new COBParameter(1L, 5L); - when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter); - when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) + when(executionContext.get(LoanCOBConstant.COB_PARAMETER)).thenReturn(loanCOBParameter); + when(retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) .thenReturn(new ArrayList<>(List.of())); // when + then @@ -134,13 +139,14 @@ public void testLoanItemReadNoOpenLoansFound() throws Exception { public void testLoanItemReaderMultiThreadRead() throws Exception { // given ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "test", "test", "UTC", null)); - LoanItemReader loanItemReader = new LoanItemReader(loanRepository, retrieveLoanIdService, loanLockingService); + LoanItemReader loanItemReader = new LoanItemReader(loanRepository, + new BeforeStepLockingItemReaderHelper<>(retrieveIdService, loanLockingService)); when(stepExecution.getExecutionContext()).thenReturn(executionContext); when(stepExecution.getJobExecution()).thenReturn(jobExecution); when(jobExecution.getExecutionContext()).thenReturn(executionContext); COBParameter loanCOBParameter = new COBParameter(1L, 100L); - when(executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER)).thenReturn(loanCOBParameter); - when(retrieveLoanIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) + when(executionContext.get(LoanCOBConstant.COB_PARAMETER)).thenReturn(loanCOBParameter); + when(retrieveIdService.retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(loanCOBParameter, false)) .thenReturn(new ArrayList<>(IntStream.rangeClosed(1, 100).boxed().map(Long::valueOf).toList())); List accountLocks = IntStream.rangeClosed(1, 100).boxed().map(Long::valueOf) .map(l -> new LoanAccountLock(l, LockOwner.LOAN_COB_CHUNK_PROCESSING, LocalDate.of(2023, 7, 25))).toList(); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemWriterStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemWriterStepDefinitions.java index 7264d391c2e..f6f6c568114 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemWriterStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemWriterStepDefinitions.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import io.cucumber.java8.En; +import org.apache.fineract.cob.domain.LockingService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.mockito.Mockito; @@ -30,7 +31,7 @@ public class LoanItemWriterStepDefinitions implements En { - private final LoanLockingService loanLockingService = mock(LoanLockingService.class); + private final LockingService loanLockingService = mock(LockingService.class); private final LoanRepository loanRepository = mock(LoanRepository.class); private final LoanItemWriter loanItemWriter = new LoanItemWriter(loanLockingService); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java index 2d39f1fc9aa..fb3ade8b6e3 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImplTest.java @@ -75,8 +75,7 @@ select min(id) as min, max(id) as max, page, count(id) as count from } private void testRetrieveLoanCOBPartitions(String expectedSQL, boolean isCatchup) { - RetrieveAllNonClosedLoanIdServiceImpl service = new RetrieveAllNonClosedLoanIdServiceImpl(loanRepository, - namedParameterJdbcTemplate); + RetrieveAllNonClosedIdServiceImpl service = new RetrieveAllNonClosedIdServiceImpl(loanRepository, namedParameterJdbcTemplate); LocalDate businessDate = LocalDate.parse("2023-06-28"); service.retrieveLoanCOBPartitions(1L, businessDate, isCatchup, 5); Mockito.verify(namedParameterJdbcTemplate, times(1)).query(sqlCaptor.capture(), paramsCaptor.capture(), rowMapper.capture()); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java index c910415c715..04721588135 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java @@ -36,7 +36,6 @@ import java.util.List; import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; import org.apache.fineract.cob.exceptions.AccountLockCannotBeOverruledException; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.config.FineractProperties; @@ -64,7 +63,7 @@ class InlineLoanCOBExecutorServiceImplTest { @Mock private InlineLoanCOBExecutionDataParser dataParser; @Mock - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveLoanIdService retrieveIdService; @Mock private FineractProperties fineractProperties; @Mock @@ -97,7 +96,7 @@ void shouldExceptionThrownIfLoanIsAlreadyLocked() { when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties); when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000); - when(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan)); + when(retrieveIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan)); assertThrows(AccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); } @@ -121,9 +120,9 @@ void shouldListBePartitioned() { when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(2); when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties); when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000); - when(retrieveLoanIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan1, loan2, loan3)); + when(retrieveIdService.retrieveLoanIdsBehindDateOrNull(any(), anyList())).thenReturn(List.of(loan1, loan2, loan3)); assertThrows(AccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); - verify(retrieveLoanIdService, times(2)).retrieveLoanIdsBehindDateOrNull(any(), anyList()); + verify(retrieveIdService, times(2)).retrieveLoanIdsBehindDateOrNull(any(), anyList()); } @Test @@ -139,7 +138,7 @@ void shouldOldestCloseBusinessDateReturnWithCorrectDate() } private Method getOldestCOBBusinessDate() throws NoSuchMethodException { - Method method = InlineLoanCOBExecutorServiceImpl.class.getDeclaredMethod("getOldestCOBBusinessDate", List.class); + Method method = InlineCommonLockableCOBExecutorService.class.getDeclaredMethod("getOldestCOBBusinessDate", List.class); method.setAccessible(true); return method; } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/SpringConfigTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/SpringConfigTest.java new file mode 100644 index 00000000000..a7d02defb2c --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/SpringConfigTest.java @@ -0,0 +1,316 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@DisplayName("SpringConfig Thread Pool Tests") +class SpringConfigTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withUserConfiguration(SpringConfig.class) + .withBean(ThreadPoolTaskExecutorBuilder.class, ThreadPoolTaskExecutorBuilder::new) + .withInitializer(context -> context.getBeanFactory().registerSingleton("propertySourcesPlaceholderConfigurer", + new org.springframework.context.support.PropertySourcesPlaceholderConfigurer())); + + @Test + @DisplayName("SimpleAsyncTaskExecutor creates unbounded threads") + void simpleAsyncTaskExecutorCreatesUnboundedThreads() throws Exception { + String prefix = "SimpleAsync-"; + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(prefix); + + CountDownLatch startGate = new CountDownLatch(1); + CountDownLatch readyLatch = new CountDownLatch(100); + CountDownLatch doneLatch = new CountDownLatch(100); + + for (int i = 0; i < 100; i++) { + executor.execute(() -> { + readyLatch.countDown(); + try { + startGate.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + doneLatch.countDown(); + } + }); + } + + boolean allStarted = readyLatch.await(5, TimeUnit.SECONDS); + assertThat(allStarted).as("All 100 threads should start").isTrue(); + + long asyncThreadCount = Thread.getAllStackTraces().keySet().stream().filter(t -> t.getName().startsWith(prefix)).count(); + + startGate.countDown(); + boolean allFinished = doneLatch.await(5, TimeUnit.SECONDS); + + assertThat(asyncThreadCount).as("Unbounded executor creates ~100 threads for 100 tasks").isGreaterThan(90); + assertThat(allFinished).isTrue(); + } + + @Test + @DisplayName("ThreadPoolTaskExecutor bounds thread creation at maxPoolSize") + void threadPoolTaskExecutorBoundsThreadCreation() throws Exception { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(500); + executor.setThreadNamePrefix("Bounded-"); + executor.initialize(); + + try { + CountDownLatch latch = new CountDownLatch(500); + Set threadNames = ConcurrentHashMap.newKeySet(); + + for (int i = 0; i < 500; i++) { + executor.execute(() -> { + threadNames.add(Thread.currentThread().getName()); + try { + Thread.sleep(5); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + latch.countDown(); + }); + } + assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); + + assertThat(threadNames.size()).as("Thread count capped at maxPoolSize").isLessThanOrEqualTo(10); + assertThat(threadNames.size()).as("Parallelism proof: multiple threads used").isGreaterThan(1); + } finally { + executor.shutdown(); + } + } + + @Test + @DisplayName("Smart defaults: CPU-aware pool sizing") + void smartDefaultsUseCpuAwarePoolSizing() { + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + SpringConfig config = new SpringConfig(); + + ThreadPoolTaskExecutor executor = config.fineractEventExecutor(builder, -1, -1, -1); + + int expectedCore = Runtime.getRuntime().availableProcessors() * 2; + int expectedMax = Runtime.getRuntime().availableProcessors() * 5; + + assertThat(executor.getCorePoolSize()).isEqualTo(expectedCore); + assertThat(executor.getMaxPoolSize()).isEqualTo(expectedMax); + assertThat(executor.getQueueCapacity()).isEqualTo(100); + } + + @Test + @DisplayName("User properties override smart defaults") + void userPropertiesOverrideSmartDefaults() { + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + SpringConfig config = new SpringConfig(); + + ThreadPoolTaskExecutor executor = config.fineractEventExecutor(builder, 16, 40, 200); + + assertThat(executor.getCorePoolSize()).isEqualTo(16); + assertThat(executor.getMaxPoolSize()).isEqualTo(40); + assertThat(executor.getQueueCapacity()).isEqualTo(200); + } + + @Test + @DisplayName("Absolute safety: maxSize adjusted when user sets high core but low max") + void absoluteSafetyAdjustsMaxWhenUserSetsHighCoreButLowMax() { + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + SpringConfig config = new SpringConfig(); + + ThreadPoolTaskExecutor executor = config.fineractEventExecutor(builder, 50, 40, -1); + + assertThat(executor.getCorePoolSize()).isEqualTo(50); + assertThat(executor.getMaxPoolSize()).as("Max adjusted to match core").isEqualTo(50); + } + + @Test + @DisplayName("Absolute safety: maxSize adjusted when smart default too small for user core") + void absoluteSafetyAdjustsMaxWhenSmartDefaultTooSmallForUserCore() { + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + SpringConfig config = new SpringConfig(); + + int cpus = Runtime.getRuntime().availableProcessors(); + int smartMax = cpus * 5; + + ThreadPoolTaskExecutor executor = config.fineractEventExecutor(builder, smartMax + 10, -1, -1); + + assertThat(executor.getCorePoolSize()).isEqualTo(smartMax + 10); + assertThat(executor.getMaxPoolSize()).as("Max adjusted above smart default to match core").isEqualTo(smartMax + 10); + } + + @Test + @DisplayName("CallerRunsPolicy provides backpressure when pool is saturated") + void threadPoolWithCallerRunsPolicyProvidesBackpressure() throws Exception { + String mainThread = Thread.currentThread().getName(); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(10); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + + try { + AtomicInteger callerExecutions = new AtomicInteger(0); + CountDownLatch latch = new CountDownLatch(50); + + for (int i = 0; i < 50; i++) { + executor.execute(() -> { + if (Thread.currentThread().getName().equals(mainThread)) { + callerExecutions.incrementAndGet(); + } + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + latch.countDown(); + }); + } + assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); + + assertThat(callerExecutions.get()).as("Caller thread executed rejected tasks").isGreaterThan(0); + } finally { + executor.shutdown(); + } + } + + @Test + @DisplayName("Configured executor has CallerRunsPolicy rejection handler") + void configuredExecutorHasCallerRunsPolicy() { + ThreadPoolTaskExecutorBuilder builder = new ThreadPoolTaskExecutorBuilder(); + SpringConfig config = new SpringConfig(); + + ThreadPoolTaskExecutor executor = config.fineractEventExecutor(builder, -1, -1, -1); + executor.initialize(); + + try { + assertThat(executor.getThreadPoolExecutor().getRejectedExecutionHandler()) + .isInstanceOf(ThreadPoolExecutor.CallerRunsPolicy.class); + } finally { + executor.shutdown(); + } + } + + @Test + @DisplayName("Bounded pool prevents thread explosion under 500 concurrent tasks") + void boundedPoolPreventsThreadExplosionUnderHighLoad() throws Exception { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("FineractEvent-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + + try { + int taskCount = 500; + CountDownLatch latch = new CountDownLatch(taskCount); + Set threadNames = ConcurrentHashMap.newKeySet(); + + for (int i = 0; i < taskCount; i++) { + executor.execute(() -> { + threadNames.add(Thread.currentThread().getName()); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + latch.countDown(); + }); + } + + assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue(); + + long poolThreadCount = threadNames.stream().filter(name -> name.startsWith("FineractEvent-")).count(); + + assertThat(poolThreadCount).as("Pool creates max 10 threads, not 500").isLessThanOrEqualTo(10); + + assertThat(poolThreadCount).as("Pool threads were created").isGreaterThan(0); + + boolean mainThreadHelped = threadNames.stream().anyMatch(name -> !name.startsWith("FineractEvent-")); + + assertThat(mainThreadHelped).as("Main thread executed rejected tasks (CallerRunsPolicy)").isTrue(); + } finally { + executor.shutdown(); + } + } + + @Test + @DisplayName("CallerRunsPolicy provides natural backpressure when pool saturated") + void callerRunsPolicyProvidesBackpressureUnderLoad() throws Exception { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(10); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + + try { + String mainThreadName = Thread.currentThread().getName(); + AtomicInteger mainThreadExecutions = new AtomicInteger(0); + CountDownLatch latch = new CountDownLatch(50); + + for (int i = 0; i < 50; i++) { + executor.execute(() -> { + if (Thread.currentThread().getName().equals(mainThreadName)) { + mainThreadExecutions.incrementAndGet(); + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + latch.countDown(); + }); + } + + assertThat(latch.await(30, TimeUnit.SECONDS)).as("All tasks completed despite saturation").isTrue(); + + assertThat(mainThreadExecutions.get()).as("Main thread executed rejected tasks (natural backpressure)").isGreaterThan(0); + } finally { + executor.shutdown(); + } + } + + @ParameterizedTest(name = "Mode: {0}") + @DisplayName("Event executor is active in all Fineract modes") + @CsvSource({ "fineract.mode.read-enabled=true", "fineract.mode.write-enabled=true", "fineract.mode.batch-worker-enabled=true", + "fineract.mode.batch-manager-enabled=true" }) + void verifyExecutorIsActiveInMode(String modeProperty) { + contextRunner.withPropertyValues(modeProperty).run(context -> { + assertThat(context).hasBean("fineractEventExecutor"); + ThreadPoolTaskExecutor executor = context.getBean("fineractEventExecutor", ThreadPoolTaskExecutor.class); + assertThat(executor.getThreadNamePrefix()).isEqualTo("FineractEvent-"); + }); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java index 44be562808f..a1cd828842f 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java @@ -24,21 +24,27 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; +import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.apache.fineract.infrastructure.core.service.database.DatabaseType; import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator; +import org.apache.fineract.infrastructure.dataqueries.data.EntityTables; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.search.service.SearchUtil; @@ -245,4 +251,51 @@ void testRegisterColumnCodeMappingUsesParameterizedQuery() { assertTrue(sql.contains("INSERT INTO x_table_column_code_mappings"), "SQL should insert into code mappings table"); assertTrue(sql.contains("VALUES (?, ?)"), "SQL should use ? placeholders"); } + + @Test + void testCreateDatatableUsesUtf8mb4UnicodeCiForMySql() { + final JsonElement payload = JsonParser.parseString(""" + { + "datatableName": "dt_charset_test", + "apptableName": "m_client", + "entitySubType": "PERSON", + "multiRow": false, + "columns": [ + { + "name": "itsAString", + "type": "String", + "mandatory": true, + "length": 10 + } + ] + } + """); + + final JsonCommand command = mock(JsonCommand.class); + when(command.json()).thenReturn(payload.toString()); + when(command.commandId()).thenReturn(1L); + + when(databaseTypeResolver.isMySQL()).thenReturn(true); + when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.MYSQL); + when(configurationDomainService.isConstraintApproachEnabledForDatatables()).thenReturn(false); + when(sqlGenerator.currentSchema()).thenReturn("database()"); + when(sqlGenerator.escape(anyString())).thenAnswer(invocation -> "`" + invocation.getArgument(0) + "`"); + when(datatableUtil.resolveEntity("m_client")).thenReturn(EntityTables.CLIENT); + when(datatableUtil.getFKField(EntityTables.CLIENT)).thenReturn("client_id"); + when(fromJsonHelper.parse(anyString())).thenReturn(payload); + when(fromJsonHelper.extractJsonArrayNamed(eq("columns"), eq(payload))) + .thenReturn(payload.getAsJsonObject().getAsJsonArray("columns")); + when(fromJsonHelper.extractStringNamed(eq("datatableName"), eq(payload))).thenReturn("dt_charset_test"); + when(fromJsonHelper.extractStringNamed(eq("entitySubType"), eq(payload))).thenReturn("PERSON"); + when(fromJsonHelper.extractStringNamed(eq("apptableName"), eq(payload))).thenReturn("m_client"); + when(fromJsonHelper.extractBooleanNamed(eq("multiRow"), eq(payload))).thenReturn(false); + when(jdbcTemplate.queryForObject(anyString(), eq(String.class), eq("dt_charset_test"))).thenReturn("true"); + + underTest.createDatatable(command); + + final ArgumentCaptor sqlCaptor = ArgumentCaptor.forClass(String.class); + verify(jdbcTemplate).execute(sqlCaptor.capture()); + assertTrue(sqlCaptor.getValue().contains("ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_UNICODE_CI;"), + "MySQL table creation must include utf8mb4 charset and utf8mb4_unicode_ci collation"); + } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java index b80cd8b4bc9..f9d7bf1de62 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java @@ -113,7 +113,7 @@ public void givenAllConfigurationWhenValidatedThenValidationSuccessful() throws "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent", "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent", "LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", "LoanApprovedAmountChangedBusinessEvent", - "SavingsAccountsStayedLockedBusinessEvent"); + "SavingsAccountsStayedLockedBusinessEvent", "SavingsAccountForceWithdrawalBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); @@ -209,7 +209,7 @@ public void givenMissingEventConfigurationWhenValidatedThenThrowException() thro "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent", "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent", "LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent", "LoanApprovedAmountChangedBusinessEvent", - "SavingsAccountsStayedLockedBusinessEvent"); + "SavingsAccountsStayedLockedBusinessEvent", "SavingsAccountForceWithdrawalBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java index 4bf1f3a807f..0751ed415e6 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java @@ -18,8 +18,8 @@ */ package org.apache.fineract.infrastructure.jobs.filter; -import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_GLIMACCOUNT_PATH_PATTERN; -import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_PATH_PATTERN; +import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelperImpl.LOAN_GLIMACCOUNT_PATH_PATTERN; +import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelperImpl.LOAN_PATH_PATTERN; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; @@ -49,9 +49,9 @@ import java.util.Optional; import java.util.UUID; import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.cob.service.RetrieveLoanIdService; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; @@ -86,7 +86,7 @@ class LoanCOBApiFilterTest { private LoanCOBApiFilter testObj; @InjectMocks - private LoanCOBFilterHelper helper; + private LoanCOBFilterHelperImpl helper; @Mock private LoanAccountLockService loanAccountLockService; @Mock @@ -104,7 +104,7 @@ class LoanCOBApiFilterTest { @Mock private LoanRescheduleRequestRepository loanRescheduleRequestRepository; @Mock - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveLoanIdService retrieveIdService; @BeforeEach public void setUp() { @@ -182,7 +182,7 @@ void shouldProceedWhenUrlDoesNotMatchWithInvalidLoanId() throws ServletException given(context.authenticatedUser()).willReturn(appUser); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); @@ -230,7 +230,7 @@ void shouldProceedWhenLoanIsNotLockedAndNoLoanIsBehind() throws ServletException given(context.authenticatedUser()).willReturn(appUser); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); @@ -260,7 +260,7 @@ void shouldProceedWhenExternalLoanIsNotLockedAndNotBehind() throws ServletExcept given(loanRepository.findIdByExternalId(any())).willReturn(2L); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); @@ -291,7 +291,7 @@ void shouldProceedWhenRescheduleLoanIsNotLockedAndNotBehind() throws ServletExce given(loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(resourceId)).willReturn(Optional.of(2L)); given(context.authenticatedUser()).willReturn(appUser); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.emptyList()); testObj.doFilterInternal(request, response, filterChain); @@ -323,7 +323,7 @@ void shouldRunInlineCOBAndProceedWhenLoanIsBehind() throws ServletException, IOE given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.singletonList(result)); given(context.authenticatedUser()).willReturn(appUser); @@ -356,7 +356,7 @@ void shouldNotRunInlineCOBAndProceedWhenLoanIsNotBehind() throws ServletExceptio given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false); given(fineractProperties.getQuery()).willReturn(fineractQueryProperties); given(fineractQueryProperties.getInClauseParameterSizeLimit()).willReturn(65000); - given(retrieveLoanIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), + given(retrieveIdService.retrieveLoanIdsBehindDate(eq(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)), anyList())).willReturn(Collections.emptyList()); given(context.authenticatedUser()).willReturn(appUser); diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperTest.java index 9c9a38fe6a2..d419365fa23 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelperTest.java @@ -22,9 +22,9 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.List; -import org.apache.fineract.cob.loan.RetrieveLoanIdService; import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.cob.service.RetrieveIdService; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.http.BodyCachingHttpServletRequestWrapper; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; @@ -56,13 +56,13 @@ public class LoanCOBFilterHelperTest { @Mock private FineractProperties fineractProperties; @Mock - private RetrieveLoanIdService retrieveLoanIdService; + private RetrieveIdService retrieveIdService; @Mock private LoanRescheduleRequestRepository loanRescheduleRequestRepository; @InjectMocks - private LoanCOBFilterHelper helper; + private LoanCOBFilterHelperImpl helper; @BeforeEach public void initLoanCOBFilterHelper() throws Exception { diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java new file mode 100644 index 00000000000..1120dc084a8 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/handler/ExecuteJobCommandHandlerTest.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.jobs.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ExecuteJobCommandHandlerTest { + + @Mock + private JobRegisterService jobRegisterService; + + @Mock + private JsonCommand command; + + @InjectMocks + private ExecuteJobCommandHandler underTest; + + @Test + void shouldExecuteJobAndReturnCommandResult() { + // given + Long jobId = 123L; + Long commandId = 456L; + String json = "{\"includeTasks\":true}"; + when(command.entityId()).thenReturn(jobId); + when(command.commandId()).thenReturn(commandId); + when(command.json()).thenReturn(json); + + // when + CommandProcessingResult result = underTest.processCommand(command); + + // then + verify(jobRegisterService).executeJobWithParameters(jobId, json); + assertEquals(commandId, result.getCommandId()); + assertEquals(jobId, result.getResourceId()); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlBuilderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlBuilderStepDefinitions.java index d9d9152acd1..e3d95611935 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlBuilderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlBuilderStepDefinitions.java @@ -29,18 +29,18 @@ import java.sql.Date; import java.util.HashMap; import org.apache.commons.io.IOUtils; +import org.apache.fineract.mix.data.MixReportXBRLNamespaceData; import org.apache.fineract.mix.data.MixTaxonomyData; -import org.apache.fineract.mix.data.NamespaceData; -import org.apache.fineract.mix.service.NamespaceReadPlatformServiceImpl; -import org.apache.fineract.mix.service.XBRLBuilder; +import org.apache.fineract.mix.service.MixReportXBRLBuilder; +import org.apache.fineract.mix.service.MixReportXBRLNamespaceReadServiceImpl; import org.apache.fineract.template.service.TemplateServiceStepDefinitions; import org.mockito.ArgumentMatchers; public class MixXbrlBuilderStepDefinitions implements En { - private NamespaceReadPlatformServiceImpl readNamespaceService; + private MixReportXBRLNamespaceReadServiceImpl readNamespaceService; - private final XBRLBuilder xbrlBuilder = new XBRLBuilder(); + private MixReportXBRLBuilder xbrlBuilder; private Date start; @@ -55,9 +55,11 @@ public class MixXbrlBuilderStepDefinitions implements En { public MixXbrlBuilderStepDefinitions() { Given("/^The XBRL input parameters start date (.*), end date (.*), currency (.*), taxonomy (.*) and sample (.*)$/", (String start, String end, String currency, String taxonomy, String sample) -> { - readNamespaceService = mock(NamespaceReadPlatformServiceImpl.class); + readNamespaceService = mock(MixReportXBRLNamespaceReadServiceImpl.class); lenient().when(this.readNamespaceService.retrieveNamespaceByPrefix(ArgumentMatchers.anyString())) - .thenReturn(new NamespaceData().setId(1L).setPrefix("mockedprefix").setUrl("mockedurl")); + .thenReturn(new MixReportXBRLNamespaceData().setId(1L).setPrefix("mockedprefix").setUrl("mockedurl")); + + xbrlBuilder = new MixReportXBRLBuilder(readNamespaceService); this.start = Date.valueOf(start); this.end = Date.valueOf(end); diff --git a/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlTaxonomyStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlTaxonomyStepDefinitions.java index 8b5c4adf985..b4772138669 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlTaxonomyStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/mix/report/MixXbrlTaxonomyStepDefinitions.java @@ -23,13 +23,13 @@ import io.cucumber.java8.En; import java.util.List; -import org.apache.fineract.mix.service.XBRLResultServiceImpl; +import org.apache.fineract.mix.service.MixReportXBRLResultServiceImpl; import org.assertj.core.util.Arrays; import org.springframework.jdbc.core.JdbcTemplate; public class MixXbrlTaxonomyStepDefinitions implements En { - private XBRLResultServiceImpl readService; + private MixReportXBRLResultServiceImpl readService; private String template; @@ -37,7 +37,7 @@ public class MixXbrlTaxonomyStepDefinitions implements En { public MixXbrlTaxonomyStepDefinitions() { Given("/^A XBRL template (.*)$/", (String template) -> { - this.readService = new XBRLResultServiceImpl(mock(JdbcTemplate.class), null, null); + this.readService = new MixReportXBRLResultServiceImpl(mock(JdbcTemplate.class), null, null); this.template = template; }); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java index 8c1afd70eea..6b217bcc26a 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanAdjustmentServiceImplTest.java @@ -33,7 +33,7 @@ import java.util.List; import java.util.Set; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; -import org.apache.fineract.cob.service.LoanAccountLockService; +import org.apache.fineract.cob.service.AccountLockService; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.domain.ExternalId; @@ -173,7 +173,7 @@ class LoanAdjustmentServiceImplTest { @Mock private LoanLifecycleStateMachine loanLifecycleStateMachine; @Mock - private LoanAccountLockService loanAccountLockService; + private AccountLockService loanAccountLockService; @Mock private ExternalIdFactory externalIdFactory; @Mock diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 541be16325b..11f23fc38c2 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -56,7 +56,7 @@ fineract.partitioned-job.partitioned-job-properties[0].thread-pool-core-pool-siz fineract.partitioned-job.partitioned-job-properties[0].thread-pool-max-pool-size=1 fineract.partitioned-job.partitioned-job-properties[0].thread-pool-queue-capacity=1 fineract.partitioned-job.partitioned-job-properties[0].retry-limit=5 -fineract.partitioned-job.partitioned-job-properties[0].poll-interval=10000 +fineract.partitioned-job.partitioned-job-properties[0].poll-interval=500 fineract.remote-job-message-handler.spring-events.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_SPRING_EVENTS_ENABLED:true} fineract.remote-job-message-handler.jms.enabled=${FINERACT_REMOTE_JOB_MESSAGE_HANDLER_JMS_ENABLED:false} diff --git a/fineract-rates/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java b/fineract-rates/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java index 1c097cb188f..ee1a66b044b 100644 --- a/fineract-rates/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java +++ b/fineract-rates/src/main/java/org/apache/fineract/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java @@ -97,13 +97,14 @@ private final class FloatingRateRowMapper implements RowMapper private final boolean addRatePeriods; - private final StringBuilder sqlQuery = new StringBuilder().append("rate.id as id, ").append("rate.name as name, ") - .append("rate.is_base_lending_rate as isBaseLendingRate, ").append("rate.is_active as isActive, ") - .append("crappu.username as createdBy, ").append("rate.created_date as createdOn, ") - .append("rate.created_on_utc as createdOnUTC, ").append("moappu.username as modifiedBy, ") - .append("rate.lastmodified_date as modifiedOn, ").append("rate.last_modified_on_utc as modifiedOnUTC ") - .append("FROM m_floating_rates as rate ").append("LEFT JOIN m_appuser as crappu on rate.created_by = crappu.id ") - .append("LEFT JOIN m_appuser as moappu on rate.last_modified_by = moappu.id "); + private static final String FLOATING_RATE_SCHEMA = """ + rate.id as id, rate.name as name, + rate.is_base_lending_rate as isBaseLendingRate, rate.is_active as isActive, + crappu.username as createdBy, rate.created_date as createdOn, + rate.created_on_utc as createdOnUTC, moappu.username as modifiedBy, + rate.lastmodified_date as modifiedOn, rate.last_modified_on_utc as modifiedOnUTC + FROM m_floating_rates as rate LEFT JOIN m_appuser as crappu on rate.created_by = crappu.id + LEFT JOIN m_appuser as moappu on rate.last_modified_by = moappu.id\s"""; FloatingRateRowMapper(final boolean addRatePeriods) { this.addRatePeriods = addRatePeriods; @@ -135,21 +136,22 @@ public FloatingRateData mapRow(final ResultSet rs, @SuppressWarnings("unused") f } public String schema() { - return sqlQuery.toString(); + return FLOATING_RATE_SCHEMA; } } private static final class FloatingRatePeriodRowMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder().append("period.id as id, ").append("period.from_date as fromDate, ") - .append("period.interest_rate as interestRate, ") - .append("period.is_differential_to_base_lending_rate as isDifferentialToBaseLendingRate, ") - .append("period.is_active as isActive, ").append("crappu.username as createdBy, ") - .append("period.created_date as createdOn, ").append("period.created_on_utc as createdOnUTC, ") - .append("moappu.username as modifiedBy, ").append("period.lastmodified_date as modifiedOn, ") - .append("period.last_modified_on_utc as modifiedOnUTC ").append("FROM m_floating_rates_periods as period ") - .append("LEFT JOIN m_appuser as crappu on period.created_by = crappu.id ") - .append("LEFT JOIN m_appuser as moappu on period.last_modified_by = moappu.id "); + private static final String FLOATING_RATE_PERIOD_SCHEMA = """ + period.id as id, period.from_date as fromDate, + period.interest_rate as interestRate, + period.is_differential_to_base_lending_rate as isDifferentialToBaseLendingRate, + period.is_active as isActive, crappu.username as createdBy, + period.created_date as createdOn, period.created_on_utc as createdOnUTC, + moappu.username as modifiedBy, period.lastmodified_date as modifiedOn, + period.last_modified_on_utc as modifiedOnUTC FROM m_floating_rates_periods as period + LEFT JOIN m_appuser as crappu on period.created_by = crappu.id + LEFT JOIN m_appuser as moappu on period.last_modified_by = moappu.id\s"""; @Override public FloatingRatePeriodData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { @@ -171,14 +173,15 @@ public FloatingRatePeriodData mapRow(final ResultSet rs, @SuppressWarnings("unus } public String schema() { - return sqlQuery.toString(); + return FLOATING_RATE_PERIOD_SCHEMA; } } private static final class FloatingRateLookupMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder().append("rate.id as id, ").append("rate.name as name, ") - .append("rate.is_base_lending_rate as isBaseLendingRate ").append("FROM m_floating_rates as rate "); + private static final String FLOATING_RATE_LOOKUP_SCHEMA = """ + rate.id as id, rate.name as name, + rate.is_base_lending_rate as isBaseLendingRate FROM m_floating_rates as rate\s"""; @Override public FloatingRateData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { @@ -189,35 +192,46 @@ public FloatingRateData mapRow(final ResultSet rs, @SuppressWarnings("unused") f } public String schema() { - return sqlQuery.toString(); + return FLOATING_RATE_LOOKUP_SCHEMA; } } private static final class FloatingInterestRatePeriodRowMapper implements RowMapper { - private final StringBuilder sqlQuery = new StringBuilder().append("select ") - .append(" linkedrateperiods.from_date as linkedrateperiods_from_date, ") - .append(" linkedrateperiods.interest_rate as linkedrateperiods_interest_rate, ") - .append(" linkedrateperiods.is_differential_to_base_lending_rate as linkedrateperiods_is_differential_to_base_lending_rate, ") - .append(" baserate.from_date as baserate_from_date, ").append(" baserate.interest_rate as baserate_interest_rate ") - .append(" from m_product_loan as lp ") - .append(" join m_product_loan_floating_rates as plfr on lp.id = plfr.loan_product_id ") - .append(" join m_floating_rates as linkedrate on linkedrate.id = plfr.floating_rates_id ") - .append("left join m_floating_rates_periods as linkedrateperiods on (linkedrate.id = linkedrateperiods.floating_rates_id and linkedrateperiods.is_active = true) ") - .append("left join ( ").append(" select blr.name, ").append(" blr.is_base_lending_rate, ") - .append(" blr.is_active, ").append(" blrperiods.from_date, ").append(" blrperiods.interest_rate ") - .append(" from m_floating_rates as blr ") - .append(" left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = true) ") - .append(" where blr.is_base_lending_rate = true and blr.is_active = true ") - .append(") as baserate on (linkedrateperiods.is_differential_to_base_lending_rate = true and linkedrate.is_base_lending_rate = false) ") - .append("where (baserate.from_date is null ").append(" or baserate.from_date = (select MAX(b.from_date) ") - .append(" from (select blr.name, ").append(" blr.is_base_lending_rate, ") - .append(" blr.is_active, ").append(" blrperiods.from_date, ") - .append(" blrperiods.interest_rate ").append(" from m_floating_rates as blr ") - .append(" left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = true) ") - .append(" where blr.is_base_lending_rate = true and blr.is_active = true ").append(" ) as b ") - .append(" where b.from_date <= linkedrateperiods.from_date)) ").append("and lp.id = ? ") - .append("order by linkedratePeriods_from_date desc "); + private static final String FLOATING_INTEREST_RATE_PERIOD_SCHEMA = """ + select + linkedrateperiods.from_date as linkedrateperiods_from_date, + linkedrateperiods.interest_rate as linkedrateperiods_interest_rate, + linkedrateperiods.is_differential_to_base_lending_rate as linkedrateperiods_is_differential_to_base_lending_rate, + baserate.from_date as baserate_from_date, baserate.interest_rate as baserate_interest_rate + from m_product_loan as lp + join m_product_loan_floating_rates as plfr on lp.id = plfr.loan_product_id + join m_floating_rates as linkedrate on linkedrate.id = plfr.floating_rates_id + left join m_floating_rates_periods as linkedrateperiods on (linkedrate.id = linkedrateperiods.floating_rates_id and linkedrateperiods.is_active = true) + left join ( + select blr.name, + blr.is_base_lending_rate, + blr.is_active, + blrperiods.from_date, + blrperiods.interest_rate + from m_floating_rates as blr + left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = true) + where blr.is_base_lending_rate = true and blr.is_active = true + ) as baserate on (linkedrateperiods.is_differential_to_base_lending_rate = true and linkedrate.is_base_lending_rate = false) + where (baserate.from_date is null + or baserate.from_date = (select MAX(b.from_date) + from (select blr.name, + blr.is_base_lending_rate, + blr.is_active, + blrperiods.from_date, + blrperiods.interest_rate + from m_floating_rates as blr + left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = true) + where blr.is_base_lending_rate = true and blr.is_active = true + ) as b + where b.from_date <= linkedrateperiods.from_date)) + and lp.id = ? + order by linkedratePeriods_from_date desc\s"""; @Override public InterestRatePeriodData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { @@ -232,7 +246,7 @@ public InterestRatePeriodData mapRow(final ResultSet rs, @SuppressWarnings("unus } public String schema() { - return sqlQuery.toString(); + return FLOATING_INTEREST_RATE_PERIOD_SCHEMA; } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java index a6a65ba741a..e16b8fefc0f 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChart.java @@ -177,9 +177,6 @@ public void validateChartSlabs(DataValidatorBuilder baseDataValidator) { } } - } else if (iSlabs.slabFields().isNotProperPriodEnd()) { - baseDataValidator.failWithCodeNoParameterAddedToErrorCode("chart.slabs.range.end.incorrect", iSlabs.slabFields().toPeriod(), - iSlabs.slabFields().getAmountRangeTo()); } } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java index e1a78a96dc3..c9f76033ae2 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartFields.java @@ -30,6 +30,7 @@ import jakarta.persistence.Embeddable; import java.time.LocalDate; import java.util.Map; +import lombok.Getter; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; @@ -44,9 +45,11 @@ public class InterestRateChartFields { @Column(name = "description", nullable = true) private String description; + @Getter @Column(name = "from_date", nullable = false) private LocalDate fromDate; + @Getter @Column(name = "end_date", nullable = true) private LocalDate endDate; @@ -123,14 +126,6 @@ public boolean isFromDateAfter(LocalDate compare) { return compare != null && DateUtils.isAfter(getFromDate(), compare); } - public LocalDate getFromDate() { - return this.fromDate; - } - - public LocalDate getEndDate() { - return this.endDate; - } - public boolean isOverlapping(InterestRateChartFields that) { final LocalDate thisFromDate = this.getFromDate(); LocalDate thisEndDate = this.getEndDate(); @@ -142,10 +137,8 @@ public boolean isOverlapping(InterestRateChartFields that) { final LocalDateInterval thisInterval = LocalDateInterval.create(thisFromDate, thisEndDate); final LocalDateInterval thatInterval = LocalDateInterval.create(thatFromDate, thatEndDate); - if (thisInterval.containsPortionOf(thatInterval) || thatInterval.containsPortionOf(thisInterval)) { - return true; - } - return false;// no overlapping + return thisInterval.containsPortionOf(thatInterval) || thatInterval.containsPortionOf(thisInterval);// no + // overlapping } public boolean isApplicableChartFor(final LocalDate target) { diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java index 2e97e34e93d..5e6cefd3632 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartSlabFields.java @@ -247,11 +247,6 @@ public static boolean isNotProperPeriodStart(InterestRateChartSlabFields interes && !(interestRateChartSlabFields.fromPeriod.equals(1) || interestRateChartSlabFields.fromPeriod.equals(0)); } - public boolean isNotProperPriodEnd() { - return !(this.toPeriod == null && this.amountRangeTo == null); - - } - public boolean isRateChartOverlapping(final InterestRateChartSlabFields that, final boolean isPrimaryGroupingByAmount) { boolean isPeriodOverLapping = isPeriodOverlapping(that); boolean isAmountOverLapping = isAmountOverlapping(that); diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java index 2cef4adfc79..de39dfb4d62 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/SavingsTransactionBooleanValues.java @@ -25,6 +25,19 @@ public class SavingsTransactionBooleanValues { private final boolean isApplyWithdrawFee; private final boolean isInterestTransfer; private final boolean isExceptionForBalanceCheck; + private final boolean isForceWithdrawal; + + public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction, + final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck, + final boolean isForceWithdrawal) { + + this.isAccountTransfer = isAccountTransfer; + this.isRegularTransaction = isRegularTransaction; + this.isApplyWithdrawFee = isApplyWithdrawFee; + this.isInterestTransfer = isInterestTransfer; + this.isExceptionForBalanceCheck = isExceptionForBalanceCheck; + this.isForceWithdrawal = isForceWithdrawal; + } public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean isApplyWithdrawFee, final boolean isInterestTransfer, final boolean isExceptionForBalanceCheck) { @@ -34,6 +47,7 @@ public SavingsTransactionBooleanValues(final boolean isAccountTransfer, final bo this.isApplyWithdrawFee = isApplyWithdrawFee; this.isInterestTransfer = isInterestTransfer; this.isExceptionForBalanceCheck = isExceptionForBalanceCheck; + this.isForceWithdrawal = false; } public boolean isAccountTransfer() { @@ -56,4 +70,8 @@ public boolean isExceptionForBalanceCheck() { return this.isExceptionForBalanceCheck; } + public boolean isForceWithdrawal() { + return this.isForceWithdrawal; + } + } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java index 360e64ada17..af1d02b92b8 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositTermDetail.java @@ -135,9 +135,7 @@ public boolean isMinDepositTermGreaterThanMaxDepositTerm() { if (this.minDepositTerm != null && this.maxDepositTerm != null) { final Integer minDepositInDays = this.convertToSafeDays(minDepositTerm, SavingsPeriodFrequencyType.fromInt(minDepositTermType)); final Integer maxDepositInDays = this.convertToSafeDays(maxDepositTerm, SavingsPeriodFrequencyType.fromInt(maxDepositTermType)); - if (minDepositInDays.compareTo(maxDepositInDays) > 0) { - return true; - } + return minDepositInDays.compareTo(maxDepositInDays) > 0; } return false; } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 7bad316ec96..c537c22cfcf 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -459,9 +459,10 @@ protected SavingsAccount(final Client client, final Group group, final SavingsPr * update summary details after events/transactions on a {@link SavingsAccount}. */ public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, - final SavingsHelper savingsHelper) { + final SavingsHelper savingsHelper, final ConfigurationDomainService configurationDomainService) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.savingsHelper = savingsHelper; + this.configurationDomainService = configurationDomainService; } public void setSavingsAccountTransactions(final List savingsAccountTransactions) { @@ -822,7 +823,8 @@ public List calculateInterestUsing(final MathContext mc, final Lo boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { - // no openingBalance concept supported yet but probably will to allow for migrations. + // no openingBalance concept supported yet but probably will to allow for + // migrations. // Check global configurations and 'pivot' date is null Money openingAccountBalance = backdatedTxnsAllowedTill ? Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate()) : Money.zero(this.currency); @@ -833,7 +835,8 @@ public List calculateInterestUsing(final MathContext mc, final Lo final List allPostingPeriods = new ArrayList<>(); if (hasInterestCalculation() || hasOverdraftInterestCalculation()) { // 1. default to calculate interest based on entire history OR - // 2. determine latest 'posting period' and find interest credited to that period + // 2. determine latest 'posting period' and find interest credited to that + // period // A generate list of EndOfDayBalances (not including interest postings) final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType @@ -1460,7 +1463,8 @@ public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate, final } public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException, - final List depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) { + final List depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill, + final boolean isForceWithdrawal) { List transactionsSortedByDate = null; @@ -1508,7 +1512,8 @@ public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transac // deal with potential minRequiredBalance and // enforceMinRequiredBalance if (!isException && transaction.canProcessBalanceCheck() && !isOverdraft()) { - if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { + if (runningBalance.minus(minRequiredBalance).isLessThanZero() + && !isForceWithdrawalAllowed(isForceWithdrawal, runningBalance)) { throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); } @@ -1521,7 +1526,7 @@ public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transac // interest posting // and should be checked after processing all transactions if (isOverdraft()) { - if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { + if (runningBalance.minus(minRequiredBalance).isLessThanZero() && !isForceWithdrawalAllowed(isForceWithdrawal, runningBalance)) { throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount); } } @@ -1541,6 +1546,31 @@ public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transac } } + /** + * Checks whether a force withdrawal is allowed based on the global configuration and the configured negative + * balance limit. + * + * @param isForceWithdrawal + * whether the current transaction is a force withdrawal + * @param runningBalance + * the current running balance of the account + * @return true if force withdrawal is enabled and the running balance is within the allowed negative limit + */ + private boolean isForceWithdrawalAllowed(final boolean isForceWithdrawal, final Money runningBalance) { + if (!isForceWithdrawal || this.configurationDomainService == null) { + return false; + } + if (!this.configurationDomainService.isForceWithdrawalOnSavingsAccountEnabled()) { + return false; + } + Long limit = this.configurationDomainService.retrieveForceWithdrawalOnSavingsAccountLimit(); + BigDecimal limitBd = BigDecimal.valueOf(limit); + if (limitBd.compareTo(BigDecimal.ZERO) > 0) { + limitBd = limitBd.negate(); + } + return runningBalance.getAmount().compareTo(limitBd) >= 0; + } + public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction, final List depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) { @@ -2470,14 +2500,16 @@ private LocalDate findLatestAnnualFeeTransactionDueDate() { } public void validateAccountBalanceDoesNotBecomeNegativeMinimal(final BigDecimal transactionAmount, final boolean isException) { - // final List transactionsSortedByDate = retrieveListOfTransactions(); + // final List transactionsSortedByDate = + // retrieveListOfTransactions(); Money runningBalance = this.summary.getAccountBalance(getCurrency()); Money minRequiredBalance = minRequiredBalanceDerived(getCurrency()); final BigDecimal withdrawalFee = null; // check last txn date - // In overdraft cases, minRequiredBalance can be in violation after interest posting + // In overdraft cases, minRequiredBalance can be in violation after interest + // posting // and should be checked after processing all transactions if (!isOverdraft()) { if (runningBalance.minus(minRequiredBalance).isLessThanZero()) { @@ -2708,7 +2740,8 @@ public void processAccountUponActivation(final boolean isSavingsInterestPostingA charge.updateToNextDueDateFrom(getActivationDate()); } - // auto pay the activation time charges (No need of checking the pivot date config) + // auto pay the activation time charges (No need of checking the pivot date + // config) this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, false); // TODO : AA add activation charges to actual changes list } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java index 8b297959191..965aee5eb7b 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -91,7 +91,8 @@ void processPostActiveActions(SavingsAccount account, DateTimeFormatter fmt, Set void postInterest(SavingsAccount account, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill); - // SavingsAccountData postInterest(SavingsAccountData account, boolean postInterestAs, LocalDate transactionDate, + // SavingsAccountData postInterest(SavingsAccountData account, boolean + // postInterestAs, LocalDate transactionDate, // boolean backdatedTxnsAllowedTill); SavingsAccountData postInterest(SavingsAccountData account, boolean postInterestAs, LocalDate transactionDate, @@ -118,4 +119,6 @@ SavingsAccountData postInterest(SavingsAccountData account, boolean postInterest CommandProcessingResult gsimDeposit(Long gsimId, JsonCommand command); CommandProcessingResult bulkGSIMClose(Long gsimId, JsonCommand command); + + CommandProcessingResult forceWithdrawal(Long savingsId, JsonCommand command); } diff --git a/fineract-savings/src/test/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartValidationTest.java b/fineract-savings/src/test/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartValidationTest.java index 4a88b32cb78..b2cebbcab0e 100644 --- a/fineract-savings/src/test/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartValidationTest.java +++ b/fineract-savings/src/test/java/org/apache/fineract/portfolio/interestratechart/domain/InterestRateChartValidationTest.java @@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -71,4 +75,22 @@ public void testGapBetweenRangesValidation() { // Check that arguments are present assertFalse(actualError.getArgs().isEmpty()); } + + @Test + public void testSinglePeriodChartSlabWithExplicitEndDoesNotFailValidation() { + InterestRateChartFields chartFields = InterestRateChartFields.createNew("chart", "chart", LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 12, 31), false); + InterestRateChart chart = InterestRateChart.createNew(chartFields, List.of()); + + InterestRateChartSlabFields slabFields = InterestRateChartSlabFields.createNew("Level 1", SavingsPeriodFrequencyType.MONTHS, 0, 3, + null, null, new BigDecimal("12"), "USD"); + InterestRateChartSlab.createNew(slabFields, chart); + + DataValidatorBuilder validator = new DataValidatorBuilder(dataValidationErrors).resource("savings.interestRateChart") + .parameter("slabs"); + chart.validateChartSlabs(validator); + + assertTrue(dataValidationErrors.isEmpty(), + "Expected no validation error for a single well-formed period slab with explicit toPeriod"); + } } diff --git a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java index cca62de456b..81d8c3117c4 100644 --- a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java +++ b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/api/AuthenticationApiResource.java @@ -43,6 +43,7 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; import org.apache.fineract.infrastructure.security.constants.TwoFactorConstants; import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData; +import org.apache.fineract.infrastructure.security.exception.PasswordResetRequiredException; import org.apache.fineract.infrastructure.security.service.SpringSecurityPlatformSecurityContext; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.apache.fineract.useradministration.data.RoleData; @@ -86,6 +87,7 @@ public static class AuthenticateRequest { @RequestBody(required = true, content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationRequest.class))) @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = AuthenticationApiResourceSwagger.PostAuthenticationResponse.class))) @ApiResponse(responseCode = "400", description = "Unauthenticated. Please login") + @ApiResponse(responseCode = "403", description = "Password reset required") public String authenticate(@Parameter(hidden = true) final String apiRequestBodyAsJson, @QueryParam("returnClientList") @DefaultValue("false") boolean returnClientList) { // TODO FINERACT-819: sort out Jersey so JSON conversion does not have @@ -137,6 +139,7 @@ public String authenticate(@Parameter(hidden = true) final String apiRequestBody authenticatedUserData = new AuthenticatedUserData().setUsername(request.username).setUserId(userId) .setBase64EncodedAuthenticationKey(new String(base64EncodedAuthenticationKey, StandardCharsets.UTF_8)) .setAuthenticated(true).setShouldRenewPassword(true).setTwoFactorAuthenticationRequired(isTwoFactorRequired); + throw new PasswordResetRequiredException(authenticatedUserData); } else { authenticatedUserData = new AuthenticatedUserData().setUsername(request.username).setOfficeId(officeId) diff --git a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredException.java b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredException.java new file mode 100644 index 00000000000..ecfb705e0d1 --- /dev/null +++ b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredException.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.exception; + +import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData; +import org.springframework.security.core.AuthenticationException; + +/** + * Exception thrown when a user must reset their password before proceeding. + * + * This exception is thrown during authentication when the user's credentials are valid but they are required to change + * their password (e.g., on first login or after an admin reset). It carries the authenticated user data so the client + * receives enough information to proceed with the password reset flow. + */ +public class PasswordResetRequiredException extends AuthenticationException { + + private final AuthenticatedUserData authenticatedUserData; + + public PasswordResetRequiredException(AuthenticatedUserData authenticatedUserData) { + super("Password reset required"); + this.authenticatedUserData = authenticatedUserData; + } + + public AuthenticatedUserData getAuthenticatedUserData() { + return authenticatedUserData; + } +} diff --git a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredExceptionMapper.java b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredExceptionMapper.java new file mode 100644 index 00000000000..5d808e2d089 --- /dev/null +++ b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/exception/PasswordResetRequiredExceptionMapper.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.exception; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.exception.ErrorHandler; +import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.data.AuthenticatedUserData; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * An {@link ExceptionMapper} to map {@link PasswordResetRequiredException} thrown during authentication into a HTTP API + * friendly format. + * + * The exception is thrown when a user's credentials are valid but they must reset their password before proceeding. + * This mapper returns a 403 FORBIDDEN response with the authenticated user data, including the + * {@code shouldRenewPassword} flag set to true. + */ +@Provider +@Component +@Scope("singleton") +@Slf4j +@RequiredArgsConstructor +public class PasswordResetRequiredExceptionMapper implements ExceptionMapper { + + private final ToApiJsonSerializer apiJsonSerializer; + + @Override + public Response toResponse(final PasswordResetRequiredException exception) { + log.warn("Exception occurred", ErrorHandler.findMostSpecificException(exception)); + return Response.status(Status.FORBIDDEN).entity(apiJsonSerializer.serialize(exception.getAuthenticatedUserData())) + .type(MediaType.APPLICATION_JSON).build(); + } +} diff --git a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java index 31630595f31..4493acd1ccc 100644 --- a/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java +++ b/fineract-security/src/main/java/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.java @@ -49,7 +49,7 @@ public class SpringSecurityPlatformSecurityContext implements PlatformSecurityCo private final ConfigurationDomainService configurationDomainService; protected static final List EXEMPT_FROM_PASSWORD_RESET_CHECK = new ArrayList( - List.of(new CommandWrapperBuilder().updateUser(null).build())); + List.of(new CommandWrapperBuilder().changeUserPassword(null).build())); @Override public AppUser authenticatedUser() { @@ -121,7 +121,7 @@ public AppUser authenticatedUser(CommandWrapper commandWrapper) { throw new UnAuthenticatedUserException(); } - if (this.shouldCheckForPasswordForceReset(commandWrapper) && this.doesPasswordHasToBeRenewed(currentUser)) { + if (this.shouldCheckForPasswordForceReset(commandWrapper, currentUser) && this.doesPasswordHasToBeRenewed(currentUser)) { throw new ResetPasswordException(currentUser.getId()); } @@ -149,6 +149,10 @@ public String officeHierarchy() { @Override public boolean doesPasswordHasToBeRenewed(AppUser currentUser) { + if (currentUser.isPasswordResetRequired()) { + return true; + } + if (this.configurationDomainService.isPasswordForcedResetEnable() && !currentUser.getPasswordNeverExpires()) { Long passwordDurationDays = this.configurationDomainService.retrievePasswordLiveTime(); @@ -164,11 +168,11 @@ public boolean doesPasswordHasToBeRenewed(AppUser currentUser) { } - private boolean shouldCheckForPasswordForceReset(CommandWrapper commandWrapper) { + private boolean shouldCheckForPasswordForceReset(CommandWrapper commandWrapper, AppUser currentUser) { for (CommandWrapper commandItem : EXEMPT_FROM_PASSWORD_RESET_CHECK) { if (commandItem.actionName().equals(commandWrapper.actionName()) && commandItem.getEntityName().equals(commandWrapper.getEntityName())) { - return false; + return commandWrapper.getEntityId() == null || !commandWrapper.getEntityId().equals(currentUser.getId()); } } return true; diff --git a/fineract-war/build.gradle b/fineract-war/build.gradle index bd98f88280d..b7f5d9197fd 100644 --- a/fineract-war/build.gradle +++ b/fineract-war/build.gradle @@ -83,6 +83,7 @@ dependencies { implementation project(':fineract-investor') implementation project(':fineract-branch') implementation project(':fineract-document') + implementation project(':fineract-mix') implementation project(':fineract-charge') implementation project(':fineract-rates') implementation project(':fineract-loan') diff --git a/fineract-working-capital-loan/dependencies.gradle b/fineract-working-capital-loan/dependencies.gradle index 35563ba3059..639bf2f9ee9 100644 --- a/fineract-working-capital-loan/dependencies.gradle +++ b/fineract-working-capital-loan/dependencies.gradle @@ -18,11 +18,59 @@ */ dependencies { + implementation(project(path: ':fineract-core')) + implementation(project(path: ':fineract-loan')) + implementation(project(path: ':fineract-cob')) + implementation(project(path: ':fineract-loan')) implementation('org.apache.avro:avro') implementation( project(path: ':fineract-avro-schemas') ) + + implementation( + 'org.springframework.boot:spring-boot-starter-web', + 'org.springframework.boot:spring-boot-starter-security', + 'org.springframework.batch:spring-batch-integration', + 'org.springframework:spring-context-support', + 'jakarta.ws.rs:jakarta.ws.rs-api', + 'org.glassfish.jersey.media:jersey-media-multipart', + + 'com.google.guava:guava', + 'com.google.code.gson:gson', + + 'org.apache.commons:commons-lang3', + + 'com.jayway.jsonpath:json-path', + + 'com.github.spotbugs:spotbugs-annotations', + 'io.swagger.core.v3:swagger-annotations-jakarta', + + 'com.squareup.retrofit2:converter-gson', + + 'org.springdoc:springdoc-openapi-starter-webmvc-ui', + 'org.mapstruct:mapstruct', + + 'io.github.resilience4j:resilience4j-spring-boot3', + ) + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'org.mapstruct:mapstruct-processor' + implementation ('org.springframework.boot:spring-boot-starter-data-jpa') { + exclude group: 'org.hibernate' + } implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') { exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence' } + // testCompile dependencies are ONLY used in src/test, not src/main. + // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! + // + testImplementation( 'io.github.classgraph:classgraph' ) + testImplementation ('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'com.jayway.jsonpath', module: 'json-path' + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + exclude group: 'jakarta.activation' + exclude group: 'javax.activation' + exclude group: 'org.skyscreamer' + } + testImplementation ('org.mockito:mockito-inline') } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/CustomWorkingCapitalLoanAccountLockRepositoryImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/CustomWorkingCapitalLoanAccountLockRepositoryImpl.java new file mode 100644 index 00000000000..400f081faea --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/CustomWorkingCapitalLoanAccountLockRepositoryImpl.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CustomWorkingCapitalLoanAccountLockRepositoryImpl implements CustomLoanAccountLockRepository { + + @PersistenceContext + private EntityManager entityManager; + + private final DatabaseSpecificSQLGenerator databaseSpecificSQLGenerator; + + @Override + public void updateLoanFromAccountLocks() { + String sql = "UPDATE m_wc_loan SET last_closed_business_date = (select " + + databaseSpecificSQLGenerator.subDate("lck.lock_placed_on_cob_business_date", "1", "DAY") + + """ + from m_wc_loan_account_locks lck + where lck.loan_id = id + and lck.lock_placed_on_cob_business_date is not null + and lck.error is not null + and lck.lock_owner in ('LOAN_COB_CHUNK_PROCESSING','LOAN_INLINE_COB_PROCESSING')) + where last_closed_business_date is null and exists (select lck.loan_id + from m_wc_loan_account_locks lck where lck.loan_id = id + and lck.lock_placed_on_cob_business_date is not null and lck.error is not null + and lck.lock_owner in ('LOAN_COB_CHUNK_PROCESSING','LOAN_INLINE_COB_PROCESSING')) + """; + + entityManager.createNativeQuery(sql).executeUpdate(); + entityManager.flush(); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalAccountLockRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalAccountLockRepository.java new file mode 100644 index 00000000000..b40f1611885 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalAccountLockRepository.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +@Repository +public interface WorkingCapitalAccountLockRepository extends AccountLockRepository, + JpaRepository, JpaSpecificationExecutor {} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalLoanAccountLock.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalLoanAccountLock.java new file mode 100644 index 00000000000..8f6dc97222a --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/domain/WorkingCapitalLoanAccountLock.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "m_wc_loan_account_locks") +@Getter +@NoArgsConstructor +public class WorkingCapitalLoanAccountLock extends AccountLock { + + private static final long serialVersionUID = -5476985607461625252L; + + public WorkingCapitalLoanAccountLock(Long loanId, LockOwner lockOwner, LocalDate lockPlacedOnCobBusinessDate) { + super(loanId, lockOwner, lockPlacedOnCobBusinessDate); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemProcessor.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemProcessor.java new file mode 100644 index 00000000000..b7d537f6c17 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemProcessor.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.processor.AbstractItemProcessor; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; + +public abstract class AbstractWorkingCapitalLoanCOBWorkerItemProcessor extends AbstractItemProcessor { + + public AbstractWorkingCapitalLoanCOBWorkerItemProcessor(COBBusinessStepService cobBusinessStepService) { + super(cobBusinessStepService); + } + + @Override + public void setLastRun(WorkingCapitalLoan processedLoan) { + processedLoan.setLastClosedBusinessDate(getBusinessDate()); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemWriter.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemWriter.java new file mode 100644 index 00000000000..0dd40efbd8e --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/AbstractWorkingCapitalLoanCOBWorkerItemWriter.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import java.util.List; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.data.RepositoryItemWriter; +import org.springframework.data.repository.CrudRepository; + +@Slf4j +public abstract class AbstractWorkingCapitalLoanCOBWorkerItemWriter extends RepositoryItemWriter { + + private final LockingService loanLockingService; + + public AbstractWorkingCapitalLoanCOBWorkerItemWriter(LockingService loanLockingService, + CrudRepository repository) { + this.loanLockingService = loanLockingService; + setRepository(repository); + } + + @Override + public void write(@NonNull Chunk items) throws Exception { + if (!items.isEmpty()) { + super.write(items); + List loanIds = items.getItems().stream().map(AbstractPersistableCustom::getId).toList(); + loanLockingService.deleteByLoanIdInAndLockOwner(loanIds, getLockOwner()); + } + } + + protected abstract LockOwner getLockOwner(); + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/ApplyWorkingCapitalLoanLockTasklet.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/ApplyWorkingCapitalLoanLockTasklet.java new file mode 100644 index 00000000000..8183d5b6254 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/ApplyWorkingCapitalLoanLockTasklet.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.apache.fineract.cob.tasklet.ApplyCommonLockTasklet; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.transaction.support.TransactionTemplate; + +@Slf4j +public class ApplyWorkingCapitalLoanLockTasklet extends ApplyCommonLockTasklet { + + public ApplyWorkingCapitalLoanLockTasklet(FineractProperties fineractProperties, + LockingService loanLockingService, RetrieveIdService retrieveIdService, + TransactionTemplate transactionTemplate) { + super(fineractProperties, loanLockingService, retrieveIdService, transactionTemplate); + } + + @Override + public String getCOBParameter() { + return WorkingCapitalLoanCOBConstant.COB_PARAMETER; + } + + @Override + public LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemListener.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemListener.java new file mode 100644 index 00000000000..bdad9e32dee --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemListener.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.listener.AbstractLoanItemListener; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.springframework.transaction.support.TransactionTemplate; + +public class InlineWorkingCapitalLoanCOBWorkerItemListener extends AbstractLoanItemListener { + + public InlineWorkingCapitalLoanCOBWorkerItemListener(LockingService lockingService, + TransactionTemplate transactionTemplate) { + super(lockingService, transactionTemplate); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_INLINE_COB_PROCESSING; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemWriter.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemWriter.java new file mode 100644 index 00000000000..b47f081ae60 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/InlineWorkingCapitalLoanCOBWorkerItemWriter.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.data.repository.CrudRepository; + +@Slf4j +public class InlineWorkingCapitalLoanCOBWorkerItemWriter extends AbstractWorkingCapitalLoanCOBWorkerItemWriter { + + public InlineWorkingCapitalLoanCOBWorkerItemWriter(LockingService loanLockingService, + CrudRepository repository) { + super(loanLockingService, repository); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_INLINE_COB_PROCESSING; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalAccountLockServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalAccountLockServiceImpl.java new file mode 100644 index 00000000000..d10fd7d2009 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalAccountLockServiceImpl.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.domain.AccountLockRepository; +import org.apache.fineract.cob.domain.CustomLoanAccountLockRepository; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.service.AbstractAccountLockService; +import org.springframework.stereotype.Service; + +@Service +public class WorkingCapitalAccountLockServiceImpl extends AbstractAccountLockService { + + public WorkingCapitalAccountLockServiceImpl(AccountLockRepository loanAccountLockRepository, + CustomLoanAccountLockRepository customLoanAccountLockRepository) { + super(loanAccountLockRepository, customLoanAccountLockRepository); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBConstant.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBConstant.java new file mode 100644 index 00000000000..96d3e5d8bdb --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBConstant.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.NoArgsConstructor; +import org.apache.fineract.cob.COBConstant; + +@NoArgsConstructor +public final class WorkingCapitalLoanCOBConstant extends COBConstant { + + // Job Related Constants + public static final String WORKING_CAPITAL_JOB_NAME = "WC_LOAN_COB"; + public static final String WORKING_CAPITAL_JOB_HUMAN_READABLE_NAME = "Working Capital Loan COB"; + public static final String WORKING_CAPITAL_LOAN_COB_JOB_NAME = "WORKING_CAPITAL_LOAN_CLOSE_OF_BUSINESS"; + + // Bean Names + public static final String WORKING_CAPITAL_LOAN_COB_STEP = "workingCapitalLoanCOBStep"; + public static final String WORKING_CAPITAL_LOAN_COB_BUSINESS_STEP = "workingCapitalLoanCOBBusinessStep"; + public static final String WORKING_CAPITAL_LOAN_COB_PARTITIONER = "workingCapitalLoanCOBPartitioner"; + + public static final String WORKING_CAPITAL_LOAN_COB_WORKER_STEP = "workingCapitalLoanCOBWorkerStep"; + public static final String WORKING_CAPITAL_LOAN_COB_FLOW = "workingCapitalLoanCOBFlow"; + + public static final String INLINE_WORKING_CAPITAL_LOAN_COB_JOB_NAME = "INLINE_WORKING_CAPITAL_LOAN_COB"; + public static final String WORKING_CAPITAL_LOAN_IDS_PARAMETER_NAME = "LoanIds"; + + public static final String WORKING_CAPITAL_LOAN_COB_PARTITIONER_STEP = "Working Capital Loan COB partition - Step"; + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBCustomJobParametersResolverTasklet.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBCustomJobParametersResolverTasklet.java new file mode 100644 index 00000000000..042351ee352 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBCustomJobParametersResolverTasklet.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import static org.apache.fineract.cob.COBConstant.BUSINESS_DATE_PARAMETER_NAME; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.common.CustomJobParameterResolver; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.lang.Nullable; + +@RequiredArgsConstructor +public class WorkingCapitalLoanCOBCustomJobParametersResolverTasklet implements Tasklet { + + private final CustomJobParameterResolver customJobParameterResolver; + + @Nullable + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + customJobParameterResolver.resolve(contribution, chunkContext, BUSINESS_DATE_PARAMETER_NAME, BUSINESS_DATE_PARAMETER_NAME); + return RepeatStatus.FINISHED; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBManagerConfiguration.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBManagerConfiguration.java new file mode 100644 index 00000000000..de81d31e7b2 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBManagerConfiguration.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_JOB_HUMAN_READABLE_NAME; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_JOB_NAME; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_PARTITIONER; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_PARTITIONER_STEP; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_STEP; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_WORKER_STEP; +import static org.apache.fineract.infrastructure.jobs.service.JobName.WORKING_CAPITAL_LOAN_COB_JOB; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.common.CustomJobParameterResolver; +import org.apache.fineract.cob.conditions.BatchManagerCondition; +import org.apache.fineract.infrastructure.springbatch.PropertyService; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.listener.ExecutionContextPromotionListener; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; +import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.transaction.PlatformTransactionManager; + +@Configuration +@EnableBatchIntegration +@Conditional(BatchManagerCondition.class) +@RequiredArgsConstructor +public class WorkingCapitalLoanCOBManagerConfiguration { + + private final JobRepository jobRepository; + + private final CustomJobParameterResolver customJobParameterResolver; + + private final PlatformTransactionManager transactionManager; + private final RemotePartitioningManagerStepBuilderFactory stepBuilderFactory; + private final COBBusinessStepService cobBusinessStepService; + private final JobOperator jobOperator; + private final DirectChannel inboundRequests; + + private final DirectChannel outboundRequests; + private final PropertyService propertyService; + private final WorkingCapitalLoanRetrieveIdService retrieveIdService; + + @Bean(WORKING_CAPITAL_LOAN_COB_PARTITIONER) + @StepScope + public WorkingCapitalLoanCOBPartitioner workingCapitalLoanCOBPartitioner(@Value("#{stepExecution}") StepExecution stepExecution) { + return new WorkingCapitalLoanCOBPartitioner(jobOperator, stepExecution, WorkingCapitalLoanCOBConstant.NUMBER_OF_DAYS_BEHIND, + retrieveIdService, cobBusinessStepService, propertyService); + } + + @Bean(WORKING_CAPITAL_JOB_HUMAN_READABLE_NAME) + public Job workingCapitalLoanCOBJob(WorkingCapitalLoanCOBPartitioner workingCapitalLoanCOBPartitioner, + ExecutionContextPromotionListener customJobParametersPromotionListener) { + return new JobBuilder(WORKING_CAPITAL_LOAN_COB_JOB.name(), jobRepository) + .start(resolveCustomJobParametersForWorkingCapitalStep(customJobParametersPromotionListener)) + .next(workingCapitalLoanCOBStep(workingCapitalLoanCOBPartitioner)).incrementer(new RunIdIncrementer()) // + .build(); + } + + @Bean + public WorkingCapitalLoanCOBCustomJobParametersResolverTasklet resolveCustomJobParametersForWorkingCapitalTasklet() { + return new WorkingCapitalLoanCOBCustomJobParametersResolverTasklet(customJobParameterResolver); + } + + @Bean + public Step resolveCustomJobParametersForWorkingCapitalStep(ExecutionContextPromotionListener customJobParametersPromotionListener) { + return new StepBuilder("Resolve custom job parameters - Step", jobRepository) + .tasklet(resolveCustomJobParametersForWorkingCapitalTasklet(), transactionManager) + .listener(customJobParametersPromotionListener).build(); + } + + @Bean(WORKING_CAPITAL_LOAN_COB_STEP) + public Step workingCapitalLoanCOBStep(WorkingCapitalLoanCOBPartitioner partitioner) { + return stepBuilderFactory.get(WORKING_CAPITAL_LOAN_COB_PARTITIONER_STEP)// + .partitioner(WORKING_CAPITAL_LOAN_COB_WORKER_STEP, partitioner)// + .pollInterval(propertyService.getPollInterval(WORKING_CAPITAL_JOB_NAME))// + .outputChannel(outboundRequests).build();// + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBPartitioner.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBPartitioner.java new file mode 100644 index 00000000000..ed03b523adf --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBPartitioner.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_JOB_NAME; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_JOB_NAME; + +import java.util.Map; +import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.common.CommonPartitioner; +import org.apache.fineract.cob.data.BusinessStepNameAndOrder; +import org.apache.fineract.cob.workingcapitalloan.businessstep.WorkingCapitalLoanCOBBusinessStep; +import org.apache.fineract.infrastructure.springbatch.PropertyService; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.launch.JobOperator; +import org.springframework.batch.core.partition.support.Partitioner; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.lang.NonNull; + +@Slf4j +public class WorkingCapitalLoanCOBPartitioner extends CommonPartitioner implements Partitioner { + + private final COBBusinessStepService cobBusinessStepService; + private final PropertyService propertyService; + + public WorkingCapitalLoanCOBPartitioner(JobOperator jobOperator, StepExecution stepExecution, Long numberOfDays, + WorkingCapitalLoanRetrieveIdService retrieveIdService, COBBusinessStepService cobBusinessStepService, + PropertyService propertyService) { + super(jobOperator, stepExecution, numberOfDays, retrieveIdService); + this.cobBusinessStepService = cobBusinessStepService; + this.propertyService = propertyService; + } + + @NonNull + @Override + public Map partition(int gridSize) { + int partitionSize = propertyService.getPartitionSize(WORKING_CAPITAL_JOB_NAME); + Set cobBusinessSteps = cobBusinessStepService.getCOBBusinessSteps(WorkingCapitalLoanCOBBusinessStep.class, + WORKING_CAPITAL_LOAN_COB_JOB_NAME); + return getPartitions(partitionSize, cobBusinessSteps); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerConfiguration.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerConfiguration.java new file mode 100644 index 00000000000..407bb948d8e --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerConfiguration.java @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_JOB_NAME; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_BUSINESS_STEP; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_FLOW; +import static org.apache.fineract.cob.workingcapitalloan.WorkingCapitalLoanCOBConstant.WORKING_CAPITAL_LOAN_COB_WORKER_STEP; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.conditions.BatchWorkerCondition; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.loan.ContextAwareTaskDecorator; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.springbatch.PropertyService; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.core.job.builder.FlowBuilder; +import org.springframework.batch.core.job.flow.Flow; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.SimpleStepBuilder; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.messaging.MessageChannel; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +@Configuration +@Conditional(BatchWorkerCondition.class) +@RequiredArgsConstructor +public class WorkingCapitalLoanCOBWorkerConfiguration { + + private final RemotePartitioningWorkerStepBuilderFactory stepBuilderFactory; + private final MessageChannel inboundRequests; + private final JobRepository jobRepository; + private final PropertyService propertyService; + private final PlatformTransactionManager transactionManager; + private final TransactionTemplate transactionTemplate; + private final LockingService wpcLoanLockingService; + private final FineractProperties fineractProperties; + private final WorkingCapitalLoanRetrieveIdService retrieveIdService; + private final WorkingCapitalLoanRepository workingCapitalLoanRepository; + + @Bean(WORKING_CAPITAL_LOAN_COB_FLOW) + public Flow workingCapitalLoanCOBFlow(Step initialisationStep, Step applyWorkingCapitalLockStep, Step workingCapitalLoanCOBBusinessStep, + Step resetContextStep) { + return new FlowBuilder(WORKING_CAPITAL_LOAN_COB_FLOW).start(initialisationStep).next(applyWorkingCapitalLockStep) + .next(workingCapitalLoanCOBBusinessStep).next(resetContextStep).build(); + } + + @Bean(WORKING_CAPITAL_LOAN_COB_WORKER_STEP) + public Step workingCapitalLoanCOBWorkerStep(Flow workingCapitalLoanCOBFlow) { + return stepBuilderFactory.get(WORKING_CAPITAL_LOAN_COB_WORKER_STEP).inputChannel(inboundRequests).flow(workingCapitalLoanCOBFlow) + .build(); + } + + @Bean(WORKING_CAPITAL_LOAN_COB_BUSINESS_STEP) + @StepScope + public Step workingCapitalLoanCOBBusinessStep(@Value("#{stepExecutionContext['partition']}") String partitionName, + TaskExecutor workingCapitalCobTaskExecutor, COBBusinessStepService cobBusinessStepService) { + SimpleStepBuilder stepBuilder = new StepBuilder("Loan Business - Step:" + partitionName, + jobRepository) + .chunk(propertyService.getChunkSize(JobName.LOAN_COB.name()), transactionManager) // + .reader(new WorkingCapitalLoanCOBWorkerItemReader(workingCapitalLoanRepository, + new BeforeStepLockingItemReaderHelper<>(retrieveIdService, wpcLoanLockingService))) // + .processor(new WorkingCapitalLoanCOBWorkerItemProcessor(cobBusinessStepService)) // + .writer(new WorkingCapitalLoanCOBWorkerItemWriter(wpcLoanLockingService, workingCapitalLoanRepository)) // + .faultTolerant() // + .retry(Exception.class) // + .retryLimit(propertyService.getRetryLimit(WORKING_CAPITAL_JOB_NAME)) // + .skip(Exception.class) // + .skipLimit(propertyService.getChunkSize(WORKING_CAPITAL_JOB_NAME) + 1) // + .listener(workingCapitalLoanItemListener()) // + .transactionManager(transactionManager); + + if (propertyService.getThreadPoolMaxPoolSize(WORKING_CAPITAL_JOB_NAME) > 1) { + stepBuilder.taskExecutor(workingCapitalCobTaskExecutor); + } + + return stepBuilder.build(); + } + + @Bean + public TaskExecutor workingCapitalCobTaskExecutor() { + if (propertyService.getThreadPoolMaxPoolSize(WORKING_CAPITAL_JOB_NAME) == 1) { + return new SyncTaskExecutor(); + } + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + taskExecutor.setThreadNamePrefix("COB-Thread-"); + taskExecutor.setThreadGroupName("COB-Thread"); + taskExecutor.setCorePoolSize(propertyService.getThreadPoolCorePoolSize(WORKING_CAPITAL_JOB_NAME)); + taskExecutor.setMaxPoolSize(propertyService.getThreadPoolMaxPoolSize(WORKING_CAPITAL_JOB_NAME)); + taskExecutor.setQueueCapacity(propertyService.getThreadPoolQueueCapacity(WORKING_CAPITAL_JOB_NAME)); + taskExecutor.setAllowCoreThreadTimeOut(true); + taskExecutor.setTaskDecorator(new ContextAwareTaskDecorator()); + return taskExecutor; + } + + // Lock + @Bean + public WorkingCapitalLoanCOBWorkerItemListener workingCapitalLoanItemListener() { + return new WorkingCapitalLoanCOBWorkerItemListener(wpcLoanLockingService, transactionTemplate); + } + + @Bean("applyWorkingCapitalLockStep") + @StepScope + public Step applyWorkingCapitalLockStep(@Value("#{stepExecutionContext['partition']}") String partitionName) { + return new StepBuilder("Apply lock - Step:" + partitionName, jobRepository) + .tasklet(applyWorkingCapitalLoanLock(), transactionManager).build(); + } + + @Bean + public ApplyWorkingCapitalLoanLockTasklet applyWorkingCapitalLoanLock() { + return new ApplyWorkingCapitalLoanLockTasklet(fineractProperties, wpcLoanLockingService, retrieveIdService, transactionTemplate); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemListener.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemListener.java new file mode 100644 index 00000000000..47923eec591 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemListener.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.listener.AbstractLoanItemListener; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.springframework.transaction.support.TransactionTemplate; + +public class WorkingCapitalLoanCOBWorkerItemListener extends AbstractLoanItemListener { + + public WorkingCapitalLoanCOBWorkerItemListener(LockingService lockingService, + TransactionTemplate transactionTemplate) { + super(lockingService, transactionTemplate); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemProcessor.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemProcessor.java new file mode 100644 index 00000000000..2819e8862a4 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemProcessor.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; + +public class WorkingCapitalLoanCOBWorkerItemProcessor extends AbstractWorkingCapitalLoanCOBWorkerItemProcessor { + + public WorkingCapitalLoanCOBWorkerItemProcessor(COBBusinessStepService cobBusinessStepService) { + super(cobBusinessStepService); + } + + @BeforeStep + public void beforeStep(StepExecution stepExecution) { + setExecutionContext(stepExecution.getExecutionContext()); + setBusinessDate(stepExecution); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemReader.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemReader.java new file mode 100644 index 00000000000..5c291af19ae --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemReader.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import java.util.concurrent.LinkedBlockingQueue; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.cob.exceptions.LockedReadException; +import org.apache.fineract.cob.service.BeforeStepLockingItemReaderHelper; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; +import org.springframework.batch.item.ItemReader; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +@Slf4j +@RequiredArgsConstructor +public class WorkingCapitalLoanCOBWorkerItemReader implements ItemReader { + + private final WorkingCapitalLoanRepository repository; + private final BeforeStepLockingItemReaderHelper itemReaderHelper; + + @Setter(AccessLevel.PROTECTED) + private LinkedBlockingQueue remainingData; + + @Nullable + @Override + public WorkingCapitalLoan read() throws Exception { + final Long loanId = remainingData.poll(); + if (loanId != null) { + try { + return repository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); + } catch (Exception e) { + throw new LockedReadException(loanId, e); + } + } + return null; + } + + @BeforeStep + @SuppressWarnings({ "unchecked" }) + public void beforeStep(@NonNull StepExecution stepExecution) { + setRemainingData(itemReaderHelper.filterRemainingData(stepExecution)); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemWriter.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemWriter.java new file mode 100644 index 00000000000..f3f38cb4e40 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanCOBWorkerItemWriter.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.data.repository.CrudRepository; + +@Slf4j +public class WorkingCapitalLoanCOBWorkerItemWriter extends AbstractWorkingCapitalLoanCOBWorkerItemWriter { + + public WorkingCapitalLoanCOBWorkerItemWriter(LockingService loanLockingService, + CrudRepository repository) { + super(loanLockingService, repository); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanInlineCOBWorkerItemProcessor.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanInlineCOBWorkerItemProcessor.java new file mode 100644 index 00000000000..ef5756c82c0 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanInlineCOBWorkerItemProcessor.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; + +public class WorkingCapitalLoanInlineCOBWorkerItemProcessor extends AbstractWorkingCapitalLoanCOBWorkerItemProcessor { + + public WorkingCapitalLoanInlineCOBWorkerItemProcessor(COBBusinessStepService cobBusinessStepService) { + super(cobBusinessStepService); + } + + @BeforeStep + public void beforeStep(StepExecution stepExecution) { + setExecutionContext(stepExecution.getJobExecution().getExecutionContext()); + setBusinessDate(stepExecution); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingConfiguration.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingConfiguration.java new file mode 100644 index 00000000000..24b842fd193 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingConfiguration.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.domain.LockingService; +import org.apache.fineract.cob.domain.WorkingCapitalAccountLockRepository; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +@RequiredArgsConstructor +public class WorkingCapitalLoanLockingConfiguration { + + private final JdbcTemplate jdbcTemplate; + private final FineractProperties fineractProperties; + private final WorkingCapitalAccountLockRepository workingCapitalAccountLockRepository; + + @Bean + @ConditionalOnMissingBean + public LockingService workingCapitalLoanLockingService() { + return new WorkingCapitalLoanLockingServiceImpl(jdbcTemplate, fineractProperties, workingCapitalAccountLockRepository); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingServiceImpl.java new file mode 100644 index 00000000000..b208e9a038c --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanLockingServiceImpl.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.AbstractLockingService; +import org.apache.fineract.cob.domain.WorkingCapitalAccountLockRepository; +import org.apache.fineract.cob.domain.WorkingCapitalLoanAccountLock; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.springframework.jdbc.core.JdbcTemplate; + +@Slf4j +public class WorkingCapitalLoanLockingServiceImpl extends AbstractLockingService { + + private static final String BATCH_LOAN_LOCK_INSERT = """ + INSERT INTO m_wc_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date) VALUES (?,?,?,?,?) + """; + + private static final String BATCH_LOAN_LOCK_UPGRADE = """ + UPDATE m_wc_loan_account_locks SET version= version + 1, lock_owner = ?, lock_placed_on = ? WHERE loan_id = ? + """; + + public WorkingCapitalLoanLockingServiceImpl(JdbcTemplate jdbcTemplate, FineractProperties fineractProperties, + WorkingCapitalAccountLockRepository loanAccountLockRepository) { + super(jdbcTemplate, fineractProperties, loanAccountLockRepository); + } + + @Override + protected String getBatchLoanLockUpgrade() { + return BATCH_LOAN_LOCK_UPGRADE; + } + + @Override + protected String getBatchLoanLockInsert() { + return BATCH_LOAN_LOCK_INSERT; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdConfiguration.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdConfiguration.java new file mode 100644 index 00000000000..5391c11174d --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdConfiguration.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +@Configuration +@RequiredArgsConstructor +public class WorkingCapitalLoanRetrieveIdConfiguration { + + private final WorkingCapitalLoanRepository loanRepository; + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + @Bean("workingCapitalLoanRetrieveIdService") + @ConditionalOnMissingBean + public WorkingCapitalLoanRetrieveIdService workingCapitalLoanRetrieveIdService() { + return new WorkingCapitalLoanRetrieveIdServiceImpl(namedParameterJdbcTemplate, loanRepository); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdService.java new file mode 100644 index 00000000000..a237d7b77d7 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdService.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import org.apache.fineract.cob.service.RetrieveIdService; + +public interface WorkingCapitalLoanRetrieveIdService extends RetrieveIdService {} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdServiceImpl.java new file mode 100644 index 00000000000..4bcea542019 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/WorkingCapitalLoanRetrieveIdServiceImpl.java @@ -0,0 +1,112 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.COBConstant; +import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.cob.data.COBParameter; +import org.apache.fineract.cob.data.COBPartition; +import org.apache.fineract.cob.service.RetrieveIdService; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanRepository; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +@RequiredArgsConstructor +public class WorkingCapitalLoanRetrieveIdServiceImpl implements WorkingCapitalLoanRetrieveIdService { + + private static final Collection NON_CLOSED_LOAN_STATUSES = new ArrayList<>( + Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL, LoanStatus.APPROVED, LoanStatus.ACTIVE, + LoanStatus.TRANSFER_IN_PROGRESS, LoanStatus.TRANSFER_ON_HOLD)); + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final WorkingCapitalLoanRepository loanRepository; + + @Override + public List retrieveLoanCOBPartitions(Long numberOfDays, LocalDate businessDate, boolean isCatchUp, int partitionSize) { + StringBuilder sql = new StringBuilder(); + sql.append("select min(id) as min, max(id) as max, page, count(id) as count from "); + sql.append(" (select floor(((row_number() over(order by id))-1) / :pageSize) as page, t.* from "); + sql.append(" (select id from m_wc_loan where loan_status_id in (:statusIds) and "); + if (isCatchUp) { + sql.append("last_closed_business_date = :businessDate "); + } else { + sql.append("(last_closed_business_date = :businessDate or last_closed_business_date is null) "); + } + sql.append("order by id) t) t2 "); + sql.append("group by page "); + sql.append("order by page"); + + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("pageSize", partitionSize); + parameters.addValue("statusIds", List.of(100, 200, 300, 303, 304)); + parameters.addValue("businessDate", businessDate.minusDays(numberOfDays)); + return namedParameterJdbcTemplate.query(sql.toString(), parameters, RetrieveIdService::mapRow); + } + + @Override + public List retrieveLoanIdsBehindDate(LocalDate businessDate, List loanIds) { + return loanRepository.findAllLoansBehindByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES); + } + + @Override + public List retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List loanIds) { + return loanRepository.findAllLoansBehindOrNullByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES); + } + + @Override + public List retrieveLoanIdsOldestCobProcessed(LocalDate businessDate) { + return loanRepository.findOldestCOBProcessedLoan(businessDate, NON_CLOSED_LOAN_STATUSES); + } + + @Override + public List retrieveAllNonClosedLoansByLastClosedBusinessDateAndMinAndMaxLoanId(COBParameter loanCOBParameter, + boolean isCatchUp) { + if (isCatchUp) { + return loanRepository.findAllLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanIdAndStatuses( + loanCOBParameter.getMinAccountId(), loanCOBParameter.getMaxAccountId(), + ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(COBConstant.NUMBER_OF_DAYS_BEHIND), + NON_CLOSED_LOAN_STATUSES); + } else { + return loanRepository.findAllLoansByLastClosedBusinessDateAndMinAndMaxLoanIdAndStatuses(loanCOBParameter.getMinAccountId(), + loanCOBParameter.getMaxAccountId(), + ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE).minusDays(COBConstant.NUMBER_OF_DAYS_BEHIND), + NON_CLOSED_LOAN_STATUSES); + } + } + + @Override + public List findAllStayedLockedByCobBusinessDate(LocalDate cobBusinessDate) { + return loanRepository.findAllStayedLockedByCobBusinessDate(cobBusinessDate); + } + + @Override + public List retrieveLoanBehindOnDisbursementDate(LocalDate businessDate, List loanIds) { + return loanRepository.findAllLoansBehindOnDisbursementDate(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DummyBusinessStep.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DummyBusinessStep.java new file mode 100644 index 00000000000..cd051dd48ec --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/DummyBusinessStep.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan.businessstep; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class DummyBusinessStep extends WorkingCapitalLoanCOBBusinessStep { + + @Override + public WorkingCapitalLoan execute(WorkingCapitalLoan input) { + log.info("Executing DummyBusinessStep... WorkingCapitalLoan ID = {}", input.getId()); + return input; + } + + @Override + public String getEnumStyledName() { + return "DUMMY_BUSINESS_STEP"; + } + + @Override + public String getHumanReadableName() { + return "Dummy Business Step"; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/WorkingCapitalLoanCOBBusinessStep.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/WorkingCapitalLoanCOBBusinessStep.java new file mode 100644 index 00000000000..1899d165e8e --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/cob/workingcapitalloan/businessstep/WorkingCapitalLoanCOBBusinessStep.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.workingcapitalloan.businessstep; + +import org.apache.fineract.cob.COBBusinessStep; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; + +public abstract class WorkingCapitalLoanCOBBusinessStep implements COBBusinessStep {} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/WorkingCapitalLoanProductConstants.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/WorkingCapitalLoanProductConstants.java new file mode 100644 index 00000000000..edf395f93d9 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/WorkingCapitalLoanProductConstants.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct; + +public final class WorkingCapitalLoanProductConstants { + + private WorkingCapitalLoanProductConstants() { + // Prevent instantiation + } + + // JSON property names + public static final String nameParamName = "name"; + public static final String shortNameParamName = "shortName"; + public static final String descriptionParamName = "description"; + public static final String fundIdParamName = "fundId"; + public static final String startDateParamName = "startDate"; + public static final String closeDateParamName = "closeDate"; + public static final String externalIdParamName = "externalId"; + + // Currency + public static final String currencyCodeParamName = "currencyCode"; + public static final String digitsAfterDecimalParamName = "digitsAfterDecimal"; + public static final String inMultiplesOfParamName = "inMultiplesOf"; + + // Settings + public static final String amortizationTypeParamName = "amortizationType"; + public static final String flatPercentageAmountParamName = "flatPercentageAmount"; + public static final String delinquencyBucketIdParamName = "delinquencyBucketId"; + public static final String npvDayCountParamName = "npvDayCount"; + public static final String paymentAllocationParamName = "paymentAllocation"; + + // Term + public static final String minPrincipalParamName = "minPrincipal"; + public static final String principalParamName = "principal"; + public static final String maxPrincipalParamName = "maxPrincipal"; + public static final String minPeriodPaymentRateParamName = "minPeriodPaymentRate"; + public static final String periodPaymentRateParamName = "periodPaymentRate"; + public static final String maxPeriodPaymentRateParamName = "maxPeriodPaymentRate"; + public static final String discountParamName = "discount"; + public static final String repaymentEveryParamName = "repaymentEvery"; + public static final String repaymentFrequencyTypeParamName = "repaymentFrequencyType"; + + // Configurable attributes (same as LoanProduct) + public static final String allowAttributeOverridesParamName = "allowAttributeOverrides"; + public static final String flatPercentageAmountOverridableParamName = "flatPercentageAmount"; + public static final String delinquencyBucketClassificationOverridableParamName = "delinquencyBucketClassification"; + public static final String discountDefaultOverridableParamName = "discountDefault"; + public static final String periodPaymentFrequencyOverridableParamName = "periodPaymentFrequency"; + public static final String periodPaymentFrequencyTypeOverridableParamName = "periodPaymentFrequencyType"; + + // Resource name for permissions + public static final String RESOURCE_NAME = "WORKINGCAPITALLOANPRODUCT"; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResource.java new file mode 100644 index 00000000000..8e2d5b35783 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResource.java @@ -0,0 +1,249 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.service.WorkingCapitalLoanProductReadPlatformService; +import org.springframework.stereotype.Component; + +@Path("/v1/working-capital-loan-products") +@Component +@Tag(name = "Working Capital Loan Products", description = "A Working Capital Loan Product is a template that is used when creating a Working Capital loan. This is a separate product type from standard loan products.") +@RequiredArgsConstructor +public class WorkingCapitalLoanProductApiResource { + + private static final String RESOURCE_NAME_FOR_PERMISSIONS = WorkingCapitalLoanProductConstants.RESOURCE_NAME; + + private final PlatformSecurityContext context; + private final WorkingCapitalLoanProductReadPlatformService readPlatformService; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Create a Working Capital Loan Product", description = "Creates a new Working Capital Loan Product.\n\n" + + "Mandatory Fields: name, shortName, currencyCode, digitsAfterDecimal, inMultiplesOf, amortizationType, npvDayCount, " + + "principal, periodPaymentRate, repaymentEvery, repaymentFrequencyType\n\n" + + "Optional Fields: externalId, fundId, startDate, closeDate, description, flatPercentageAmount (required if amortizationType=FLAT), " + + "delinquencyBucketClassification, minPrincipal, maxPrincipal, minPeriodPaymentRate, maxPeriodPaymentRate, " + + "discount, paymentAllocation, allowAttributeOverrides") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PostWorkingCapitalLoanProductsRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PostWorkingCapitalLoanProductsResponse.class))) }) + public CommandProcessingResult createWorkingCapitalLoanProduct(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().createWorkingCapitalLoanProduct().withJson(apiRequestBodyAsJson) + .build(); + + return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "List Working Capital Loan Products", description = "Lists all Working Capital Loan Products") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsResponse.class)))) }) + public List retrieveAllWorkingCapitalLoanProducts() { + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); + return this.readPlatformService.retrieveAllWorkingCapitalLoanProducts(); + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve Working Capital Loan Product Details Template", operationId = "retrieveTemplateWorkingCapitalLoanProduct", description = "This is a convenience resource. It can be useful when building maintenance user interface screens for client applications. The template data returned consists of any or all of:\n" + + "\n" + "Field Defaults\n" + "Allowed description Lists\n" + "Example Request:\n" + "\n" + + "workingcapitalloanproducts/template") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsTemplateResponse.class))) }) + public WorkingCapitalLoanProductData retrieveTemplate() { + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); + return this.readPlatformService.retrieveNewWorkingCapitalLoanProductDetails(); + } + + @GET + @Path("{productId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a Working Capital Loan Product", operationId = "retrieveOneWorkingCapitalLoanProduct", description = "Retrieves a Working Capital Loan Product\n\n" + + "Example Requests:\n" + "\n" + "workingcapitalloanproducts/1\n" + "\n" + "\n" + "workingcapitalloanproducts/1?template=true\n" + + "\n" + "\n" + "workingcapitalloanproducts/1?fields=name,description,principal") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsProductIdResponse.class))) }) + public WorkingCapitalLoanProductData retrieveWorkingCapitalLoanProductDetails( + @PathParam("productId") @Parameter(description = "productId") final Long productId, @Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); + + return getWorkingCapitalLoanProductDetails(productId, uriInfo); + } + + @PUT + @Path("{productId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update a Working Capital Loan Product", operationId = "updateWorkingCapitalLoanProduct", description = "Updates a Working Capital Loan Product") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PutWorkingCapitalLoanProductsProductIdRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PutWorkingCapitalLoanProductsProductIdResponse.class))) }) + public CommandProcessingResult updateWorkingCapitalLoanProduct( + @PathParam("productId") @Parameter(description = "productId") final Long productId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + + return getUpdateWorkingCapitalLoanProductResult(apiRequestBodyAsJson, productId); + } + + @DELETE + @Path("{productId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a Working Capital Loan Product", operationId = "deleteWorkingCapitalLoanProduct", description = "Deletes a Working Capital Loan Product if it is not in use") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.DeleteWorkingCapitalLoanProductsProductIdResponse.class))) }) + public CommandProcessingResult deleteWorkingCapitalLoanProduct( + @PathParam("productId") @Parameter(description = "productId") final Long productId) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteWorkingCapitalLoanProduct(productId).build(); + return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + @DELETE + @Path("external-id/{externalProductId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete a Working Capital Loan Product", operationId = "deleteWorkingCapitalLoanProductByExternalId", description = "Deletes a Working Capital Loan Product by external ID if it is not in use") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.DeleteWorkingCapitalLoanProductsProductIdResponse.class))) }) + public CommandProcessingResult deleteWorkingCapitalLoanProduct( + @PathParam("externalProductId") @Parameter(description = "externalProductId") final String externalProductId) { + final ExternalId externalId = ExternalIdFactory.produce(externalProductId); + + final Long productId = resolveProductId(externalId); + if (productId == null) { + throw new WorkingCapitalLoanProductNotFoundException(externalId); + } + + final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteWorkingCapitalLoanProduct(productId).build(); + + return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + @GET + @Path("external-id/{externalProductId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Retrieve a Working Capital Loan Product", operationId = "retrieveOneWorkingCapitalLoanProductByExternalId", description = "Retrieves a Working Capital Loan Product by external ID\n\n" + + "Example Requests:\n" + "\n" + "workingcapitalloanproducts/external-id/2075e308-d4a8-44d9-8203-f5a947b8c2f4") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.GetWorkingCapitalLoanProductsProductIdResponse.class))) }) + public WorkingCapitalLoanProductData retrieveWorkingCapitalLoanProductDetails( + @PathParam("externalProductId") @Parameter(description = "externalProductId") final String externalProductId, + @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS); + + final ExternalId externalId = ExternalIdFactory.produce(externalProductId); + + final Long productId = resolveProductId(externalId); + if (productId == null) { + throw new WorkingCapitalLoanProductNotFoundException(externalId); + } + + return getWorkingCapitalLoanProductDetails(productId, uriInfo); + } + + @PUT + @Path("external-id/{externalProductId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Update a Working Capital Loan Product", operationId = "updateWorkingCapitalLoanProductByExternalId", description = "Updates a Working Capital Loan Product by external ID") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PutWorkingCapitalLoanProductsProductIdRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkingCapitalLoanProductApiResourceSwagger.PutWorkingCapitalLoanProductsProductIdResponse.class))) }) + public CommandProcessingResult updateWorkingCapitalLoanProduct( + @PathParam("externalProductId") @Parameter(description = "externalProductId") final String externalProductId, + @Parameter(hidden = true) final String apiRequestBodyAsJson) { + final ExternalId externalId = ExternalIdFactory.produce(externalProductId); + + final Long productId = resolveProductId(externalId); + + if (productId == null) { + throw new WorkingCapitalLoanProductNotFoundException(externalId); + } + + return getUpdateWorkingCapitalLoanProductResult(apiRequestBodyAsJson, productId); + } + + private CommandProcessingResult getUpdateWorkingCapitalLoanProductResult(final String apiRequestBodyAsJson, final Long productId) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateWorkingCapitalLoanProduct(productId) + .withJson(apiRequestBodyAsJson).build(); + + return this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } + + private Long resolveProductId(final ExternalId externalProductId) { + try { + return readPlatformService.retrieveWorkingCapitalLoanProductByExternalId(externalProductId).getId(); + } catch (Exception e) { + return null; + } + } + + private WorkingCapitalLoanProductData getWorkingCapitalLoanProductDetails(final Long productId, final UriInfo uriInfo) { + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + final WorkingCapitalLoanProductData product = this.readPlatformService.retrieveWorkingCapitalLoanProduct(productId); + if (settings.isTemplate()) { + return product.applyTemplate(readPlatformService.retrieveNewWorkingCapitalLoanProductDetails()); + } + return product; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResourceSwagger.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResourceSwagger.java new file mode 100644 index 00000000000..d0cd51d91a6 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/api/WorkingCapitalLoanProductApiResourceSwagger.java @@ -0,0 +1,457 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.fund.data.FundData; + +/** + * Swagger documentation classes for Working Capital Loan Products API. + */ +public final class WorkingCapitalLoanProductApiResourceSwagger { + + private WorkingCapitalLoanProductApiResourceSwagger() {} + + @Schema(description = "PostWorkingCapitalLoanProductsRequest") + public static final class PostWorkingCapitalLoanProductsRequest { + + private PostWorkingCapitalLoanProductsRequest() {} + + // Details category + @Schema(example = "Working Capital Product 1") + public String name; + @Schema(example = "WCP1") + public String shortName; + @Schema(example = "Working Capital Loan Product for merchants") + public String description; + @Schema(example = "2075e308-d4a8-44d9-8203-f5a947b8c2f4") + public String externalId; + @Schema(example = "3") + public Long fundId; + @Schema(example = "10 July 2022") + public String startDate; + @Schema(example = "10 July 2025") + public String closeDate; + + // Currency + @Schema(example = "USD") + public String currencyCode; + @Schema(example = "2") + public Integer digitsAfterDecimal; + @Schema(example = "1") + public Integer inMultiplesOf; + + // Core product parameters (related detail: amortization, repayment defaults) + @Schema(example = "EIR", allowableValues = { "EIR", "FLAT" }) + public String amortizationType; + @Schema(example = "5.5") + public BigDecimal flatPercentageAmount; + @Schema(example = "1") + public Long delinquencyBucketId; + @Schema(example = "365") + public Integer npvDayCount; + + // Payment allocation + public List paymentAllocation; + + // Min/max constraints and default values (related detail: principal, period rate, repayment) + @Schema(example = "1000.00") + public BigDecimal minPrincipal; + @Schema(example = "10000.00") + public BigDecimal principal; + @Schema(example = "50000.00") + public BigDecimal maxPrincipal; + @Schema(example = "0.5") + public BigDecimal minPeriodPaymentRate; + @Schema(example = "1.0") + public BigDecimal periodPaymentRate; + @Schema(example = "2.0") + public BigDecimal maxPeriodPaymentRate; + @Schema(example = "0.0") + public BigDecimal discount; + @Schema(example = "30") + public Integer repaymentEvery; + @Schema(example = "DAYS", allowableValues = { "DAYS", "MONTHS", "YEARS" }) + public String repaymentFrequencyType; + + // Configurable attributes + public PostAllowAttributeOverrides allowAttributeOverrides; + + @Schema(example = "en_GB") + public String locale; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + + @Schema(description = "PostPaymentAllocation") + public static final class PostPaymentAllocation { + + private PostPaymentAllocation() {} + + @Schema(example = "DEFAULT", allowableValues = { "DEFAULT", "REPAYMENT", "DOWN_PAYMENT", "MERCHANT_ISSUED_REFUND", + "PAYOUT_REFUND", "GOODWILL_CREDIT", "CHARGE_REFUND", "CHARGE_ADJUSTMENT", "WAIVE_INTEREST", "CHARGE_PAYMENT", + "REFUND_FOR_ACTIVE_LOAN", "INTEREST_PAYMENT_WAIVER", "INTEREST_REFUND", "CAPITALIZED_INCOME_ADJUSTMENT" }) + public String transactionType; + public List paymentAllocationOrder; + + @Schema(description = "PaymentAllocationOrder") + public static final class PaymentAllocationOrder { + + private PaymentAllocationOrder() {} + + @Schema(example = "PENALTY") + public String paymentAllocationRule; + @Schema(example = "1") + public Integer order; + } + } + + @Schema(description = "PostAllowAttributeOverrides") + public static final class PostAllowAttributeOverrides { + + private PostAllowAttributeOverrides() {} + + @Schema(example = "true") + public Boolean flatPercentageAmount; + @Schema(example = "true") + public Boolean delinquencyBucketClassification; + @Schema(example = "true") + public Boolean discountDefault; + @Schema(example = "true") + public Boolean periodPaymentFrequency; + @Schema(example = "true") + public Boolean periodPaymentFrequencyType; + } + } + + @Schema(description = "PostWorkingCapitalLoanProductsResponse") + public static final class PostWorkingCapitalLoanProductsResponse { + + private PostWorkingCapitalLoanProductsResponse() {} + + @Schema(example = "1") + public Long resourceId; + } + + @Schema(description = "GetWorkingCapitalLoanProductsResponse") + public static final class GetWorkingCapitalLoanProductsResponse { + + private GetWorkingCapitalLoanProductsResponse() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "Working Capital Product 1") + public String name; + @Schema(example = "WCP1") + public String shortName; + @Schema(example = "Working Capital Loan Product for merchants") + public String description; + @Schema(example = "3") + public Long fundId; + @Schema(example = "Fund 1") + public String fundName; + @Schema(example = "[2022, 7, 10]") + public LocalDate startDate; + @Schema(example = "[2025, 7, 10]") + public LocalDate closeDate; + @Schema(example = "2075e308-d4a8-44d9-8203-f5a947b8c2f4") + public String externalId; + @Schema(example = "ACTIVE") + public String status; + + // Currency + public CurrencyData currency; + + // Core product parameters (related detail) + public StringEnumOptionData amortizationType; + @Schema(example = "5.5") + public BigDecimal flatPercentageAmount; + public GetDelinquencyBucket delinquencyBucket; + @Schema(example = "365") + public Integer npvDayCount; + public List paymentAllocation; + + // Min/max constraints and default values (related detail) + @Schema(example = "1000.00") + public BigDecimal minPrincipal; + @Schema(example = "10000.00") + public BigDecimal principal; + @Schema(example = "50000.00") + public BigDecimal maxPrincipal; + @Schema(example = "0.5") + public BigDecimal minPeriodPaymentRate; + @Schema(example = "1.0") + public BigDecimal periodPaymentRate; + @Schema(example = "2.0") + public BigDecimal maxPeriodPaymentRate; + @Schema(example = "0.0") + public BigDecimal discount; + @Schema(example = "30") + public Integer repaymentEvery; + public StringEnumOptionData repaymentFrequencyType; + + // Configurable attributes + public GetConfigurableAttributes allowAttributeOverrides; + + @Schema(description = "GetDelinquencyBucket") + public static final class GetDelinquencyBucket { + + private GetDelinquencyBucket() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "Bucket 1") + public String name; + public List ranges; + + @Schema(description = "GetDelinquencyRange") + public static final class GetDelinquencyRange { + + private GetDelinquencyRange() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "Range 1") + public String classification; + @Schema(example = "1") + public Integer minimumAgeDays; + @Schema(example = "15") + public Integer maximumAgeDays; + } + } + + @Schema(description = "GetPaymentAllocation") + public static final class GetPaymentAllocation { + + private GetPaymentAllocation() {} + + @Schema(example = "DEFAULT") + public String transactionType; + public List paymentAllocationOrder; + + @Schema(description = "PaymentAllocationOrder") + public static final class PaymentAllocationOrder { + + private PaymentAllocationOrder() {} + + @Schema(example = "PENALTY") + public String paymentAllocationRule; + @Schema(example = "1") + public Integer order; + } + } + + @Schema(description = "GetConfigurableAttributes") + public static final class GetConfigurableAttributes { + + private GetConfigurableAttributes() {} + + @Schema(example = "true") + public Boolean flatPercentageAmount; + @Schema(example = "true") + public Boolean delinquencyBucketClassification; + @Schema(example = "true") + public Boolean discountDefault; + @Schema(example = "true") + public Boolean periodPaymentFrequency; + @Schema(example = "true") + public Boolean periodPaymentFrequencyType; + } + } + + @Schema(description = "GetWorkingCapitalLoanProductsTemplateResponse") + public static final class GetWorkingCapitalLoanProductsTemplateResponse { + + private GetWorkingCapitalLoanProductsTemplateResponse() {} + + public List fundOptions; + public List currencyOptions; + public List amortizationTypeOptions; + public List periodFrequencyTypeOptions; + public List advancedPaymentAllocationTypes; + public List advancedPaymentAllocationTransactionTypes; + public List delinquencyBucketOptions; + } + + @Schema(description = "GetWorkingCapitalLoanProductsProductIdResponse") + public static final class GetWorkingCapitalLoanProductsProductIdResponse { + + private GetWorkingCapitalLoanProductsProductIdResponse() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "Working Capital Product 1") + public String name; + @Schema(example = "WCP1") + public String shortName; + @Schema(example = "Working Capital Loan Product for merchants") + public String description; + @Schema(example = "3") + public Long fundId; + @Schema(example = "Fund 1") + public String fundName; + @Schema(example = "[2022, 7, 10]") + public LocalDate startDate; + @Schema(example = "[2025, 7, 10]") + public LocalDate closeDate; + @Schema(example = "2075e308-d4a8-44d9-8203-f5a947b8c2f4") + public String externalId; + @Schema(example = "ACTIVE") + public String status; + + // Currency + public CurrencyData currency; + + // Core product parameters (related detail) + public StringEnumOptionData amortizationType; + @Schema(example = "5.5") + public BigDecimal flatPercentageAmount; + public GetWorkingCapitalLoanProductsResponse.GetDelinquencyBucket delinquencyBucket; + @Schema(example = "365") + public Integer npvDayCount; + public List paymentAllocation; + + // Min/max constraints and default values (related detail) + @Schema(example = "1000.00") + public BigDecimal minPrincipal; + @Schema(example = "10000.00") + public BigDecimal principal; + @Schema(example = "50000.00") + public BigDecimal maxPrincipal; + @Schema(example = "0.5") + public BigDecimal minPeriodPaymentRate; + @Schema(example = "1.0") + public BigDecimal periodPaymentRate; + @Schema(example = "2.0") + public BigDecimal maxPeriodPaymentRate; + @Schema(example = "0.0") + public BigDecimal discount; + @Schema(example = "30") + public Integer repaymentEvery; + public StringEnumOptionData repaymentFrequencyType; + + // Configurable attributes + public GetWorkingCapitalLoanProductsResponse.GetConfigurableAttributes allowAttributeOverrides; + } + + @Schema(description = "PutWorkingCapitalLoanProductsProductIdRequest") + public static final class PutWorkingCapitalLoanProductsProductIdRequest { + + private PutWorkingCapitalLoanProductsProductIdRequest() {} + + // Details category + @Schema(example = "Working Capital Product 1 Updated") + public String name; + @Schema(example = "WCP1") + public String shortName; + @Schema(example = "Updated Working Capital Loan Product for merchants") + public String description; + @Schema(example = "2075e308-d4a8-44d9-8203-f5a947b8c2f4") + public String externalId; + @Schema(example = "3") + public Long fundId; + @Schema(example = "10 July 2022") + public String startDate; + @Schema(example = "10 July 2025") + public String closeDate; + + // Currency + @Schema(example = "USD") + public String currencyCode; + @Schema(example = "2") + public Integer digitsAfterDecimal; + @Schema(example = "1") + public Integer inMultiplesOf; + + // Core product parameters (related detail) + @Schema(example = "EIR", allowableValues = { "EIR", "FLAT" }) + public String amortizationType; + @Schema(example = "5.5") + public BigDecimal flatPercentageAmount; + @Schema(example = "1") + public Long delinquencyBucketId; + @Schema(example = "365") + public Integer npvDayCount; + + // Payment allocation + public List paymentAllocation; + + // Min/max constraints and default values (related detail) + @Schema(example = "1000.00") + public BigDecimal minPrincipal; + @Schema(example = "10000.00") + public BigDecimal principal; + @Schema(example = "50000.00") + public BigDecimal maxPrincipal; + @Schema(example = "0.5") + public BigDecimal minPeriodPaymentRate; + @Schema(example = "1.0") + public BigDecimal periodPaymentRate; + @Schema(example = "2.0") + public BigDecimal maxPeriodPaymentRate; + @Schema(example = "0.0") + public BigDecimal discount; + @Schema(example = "30") + public Integer repaymentEvery; + @Schema(example = "DAYS", allowableValues = { "DAYS", "MONTHS", "YEARS" }) + public String repaymentFrequencyType; + + // Configurable attributes + public PostWorkingCapitalLoanProductsRequest.PostAllowAttributeOverrides allowAttributeOverrides; + + @Schema(example = "en_GB") + public String locale; + @Schema(example = "dd MMMM yyyy") + public String dateFormat; + } + + @Schema(description = "PutWorkingCapitalLoanProductsProductIdResponse") + public static final class PutWorkingCapitalLoanProductsProductIdResponse { + + private PutWorkingCapitalLoanProductsProductIdResponse() {} + + static final class PutWorkingCapitalLoanProductChanges { + + private PutWorkingCapitalLoanProductChanges() {} + + @Schema(example = "Working Capital Product 1 Updated") + public String name; + @Schema(example = "en_GB") + public String locale; + } + + @Schema(example = "1") + public Long resourceId; + public PutWorkingCapitalLoanProductChanges changes; + } + + @Schema(description = "DeleteWorkingCapitalLoanProductsProductIdResponse") + public static final class DeleteWorkingCapitalLoanProductsProductIdResponse { + + private DeleteWorkingCapitalLoanProductsProductIdResponse() {} + + @Schema(example = "1") + public Long resourceId; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java new file mode 100644 index 00000000000..4a9ae7845f1 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductConfigurableAttributesData.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.data; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Data Transfer Object for Configurable Attributes. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkingCapitalLoanProductConfigurableAttributesData implements Serializable { + + private Boolean flatPercentageAmount; + private Boolean delinquencyBucketClassification; + private Boolean discountDefault; + private Boolean periodPaymentFrequency; + private Boolean periodPaymentFrequencyType; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductData.java new file mode 100644 index 00000000000..29fdeae7f50 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalLoanProductData.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; +import org.apache.fineract.portfolio.fund.data.FundData; + +/** + * Data Transfer Object for Working Capital Loan Product. + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WorkingCapitalLoanProductData implements Serializable { + + private Long id; + private String name; + private String shortName; + private String description; + private Long fundId; + private String fundName; + private LocalDate startDate; + private LocalDate closeDate; + private String externalId; + private String status; + + // Currency details + private CurrencyData currency; + + // Settings details + private StringEnumOptionData amortizationType; + private BigDecimal flatPercentageAmount; + private DelinquencyBucketData delinquencyBucket; + private Integer npvDayCount; + private List paymentAllocation; + + // Term details + private BigDecimal minPrincipal; + private BigDecimal principal; + private BigDecimal maxPrincipal; + private BigDecimal minPeriodPaymentRate; + private BigDecimal periodPaymentRate; + private BigDecimal maxPeriodPaymentRate; + private BigDecimal discount; + private Integer repaymentEvery; + private StringEnumOptionData repaymentFrequencyType; + + // Configurable attributes (allowAttributeOverrides) + private WorkingCapitalLoanProductConfigurableAttributesData allowAttributeOverrides; + + // Template related + private Collection fundOptions; + private Collection currencyOptions; + private List amortizationTypeOptions; + private List periodFrequencyTypeOptions; + private List advancedPaymentAllocationTypes; + private List advancedPaymentAllocationTransactionTypes; + private Collection delinquencyBucketOptions; + + public WorkingCapitalLoanProductData applyTemplate(final WorkingCapitalLoanProductData productTemplate) { + setFundOptions(productTemplate.getFundOptions()); + setCurrencyOptions(productTemplate.getCurrencyOptions()); + setAmortizationTypeOptions(productTemplate.getAmortizationTypeOptions()); + setPeriodFrequencyTypeOptions(productTemplate.getPeriodFrequencyTypeOptions()); + setAdvancedPaymentAllocationTransactionTypes(productTemplate.getAdvancedPaymentAllocationTransactionTypes()); + setAdvancedPaymentAllocationTypes(productTemplate.getAdvancedPaymentAllocationTypes()); + setDelinquencyBucketOptions(productTemplate.getDelinquencyBucketOptions()); + return this; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalPaymentAllocationData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalPaymentAllocationData.java new file mode 100644 index 00000000000..1f3c13ce5da --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/data/WorkingCapitalPaymentAllocationData.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.data; + +import java.io.Serializable; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; + +/** + * Data Transfer Object for Payment Allocation + */ +@Getter +@AllArgsConstructor +public class WorkingCapitalPaymentAllocationData implements Serializable { + + private final PaymentAllocationTransactionType transactionType; + private final List paymentAllocationOrder; + + @Getter + @AllArgsConstructor + public static class PaymentAllocationOrder implements Serializable { + + private final String paymentAllocationRule; + private final Integer order; + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsJsonParser.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsJsonParser.java new file mode 100644 index 00000000000..52d197966b5 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsJsonParser.java @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import com.google.common.base.Enums; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; + +@Service +@AllArgsConstructor +public class WorkingCapitalAdvancedPaymentAllocationsJsonParser { + + public final WorkingCapitalAdvancedPaymentAllocationsValidator validator; + + public List assembleWCPaymentAllocationRules(final JsonCommand command) { + final JsonArray paymentAllocations = command.arrayOfParameterNamed("paymentAllocation"); + List productPaymentAllocationRules = null; + if (paymentAllocations != null) { + productPaymentAllocationRules = paymentAllocations.asList().stream().map(json -> { + final Map map = json.getAsJsonObject().asMap(); + final WorkingCapitalLoanProductPaymentAllocationRule rule = new WorkingCapitalLoanProductPaymentAllocationRule(); + populatePaymentAllocationRules(map, rule); + populateTransactionType(map, rule); + return rule; + }).toList(); + } + validator.validate(productPaymentAllocationRules); + return productPaymentAllocationRules; + } + + private void populatePaymentAllocationRules(final Map map, + final WorkingCapitalLoanProductPaymentAllocationRule rule) { + final JsonArray paymentAllocationOrder = asJsonArrayOrNull(map.get("paymentAllocationOrder")); + if (paymentAllocationOrder != null) { + rule.setAllocationTypes(getPaymentAllocationTypes(paymentAllocationOrder)); + } + } + + private void populateTransactionType(final Map map, final WorkingCapitalLoanProductPaymentAllocationRule rule) { + final String transactionType = asStringOrNull(map.get("transactionType")); + if (transactionType != null) { + rule.setTransactionType(Enums.getIfPresent(PaymentAllocationTransactionType.class, transactionType).orNull()); + } + } + + @NonNull + private List getPaymentAllocationTypes(final JsonArray paymentAllocationOrder) { + if (paymentAllocationOrder != null) { + // Validate that paymentAllocationOrder is not empty + if (paymentAllocationOrder.isEmpty()) { + validator.raiseValidationError("wc-payment-allocation-order.cannot.be.empty", "Payment allocation order cannot be empty"); + } + final List> parsedListWithOrder = paymentAllocationOrder.asList().stream() + .map(json -> { + final Map map = json.getAsJsonObject().asMap(); + WorkingCapitalPaymentAllocationType paymentAllocationType = null; + final String paymentAllocationRule = asStringOrNull(map.get("paymentAllocationRule")); + if (paymentAllocationRule != null) { + paymentAllocationType = Enums.getIfPresent(WorkingCapitalPaymentAllocationType.class, paymentAllocationRule) + .orNull(); + } + return Pair.of(asIntegerOrNull(map.get("order")), paymentAllocationType); + }).toList(); + if (parsedListWithOrder.stream().anyMatch(p -> p.getLeft() == null)) { + validator.raiseValidationError("wc-payment-allocation-order.order.required", + "Each paymentAllocationOrder entry must have an 'order' field."); + } + final List> sorted = parsedListWithOrder.stream() + .sorted(Comparator.comparing(Pair::getLeft)).toList(); + validator.validatePairOfOrderAndPaymentAllocationType(sorted); + return sorted.stream().map(Pair::getRight).toList(); + } else { + return List.of(); + } + } + + private Integer asIntegerOrNull(final JsonElement element) { + if (element != null && !element.isJsonNull() && element.isJsonPrimitive()) { + return element.getAsInt(); + } + return null; + } + + private String asStringOrNull(final JsonElement element) { + if (element != null && !element.isJsonNull() && element.isJsonPrimitive()) { + return element.getAsString(); + } + return null; + } + + private JsonArray asJsonArrayOrNull(final JsonElement element) { + if (element != null && !element.isJsonNull() && element.isJsonArray()) { + return element.getAsJsonArray(); + } + return null; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsValidator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsValidator.java new file mode 100644 index 00000000000..b46600ac387 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAdvancedPaymentAllocationsValidator.java @@ -0,0 +1,109 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; +import org.springframework.stereotype.Service; + +@Service +public class WorkingCapitalAdvancedPaymentAllocationsValidator { + + public void validate(final List rules) { + if (rules == null || rules.isEmpty()) { + raiseValidationError("wc-payment-allocation-without-default", "At least one DEFAULT payment allocation must be provided"); + } + final List rulesToValidate = Objects.requireNonNull(rules); + if (hasWCPaymentAllocationRule(rulesToValidate)) { + if (!hasAtLeastOneDefaultPaymentAllocation(rulesToValidate)) { + raiseValidationError("wc-payment-allocation-without-default", "At least one DEFAULT payment allocation must be provided"); + } + + if (hasDuplicateTransactionTypes(rulesToValidate)) { + raiseValidationError("wc-payment-allocation-with-duplicate-transaction-type", + "The same transaction type must be provided only once"); + } + + for (final WorkingCapitalLoanProductPaymentAllocationRule rule : rulesToValidate) { + validateAllocationRule(rule); + } + } + } + + public void validatePairOfOrderAndPaymentAllocationType(final List> rules) { + // WCL has 3 allocation types: PENALTY, FEE, PRINCIPAL (no INTEREST) + final int expectedCount = 3; + if (rules.size() != expectedCount) { + raiseValidationError("wc-payment-allocation-order.must.contain.3.entries", + "Each provided payment allocation must contain exactly " + expectedCount + " allocation rules, but " + rules.size() + + " were provided"); + } + + // Check for null values (invalid payment allocation types) + final boolean hasNullValues = rules.stream().anyMatch(pair -> pair.getRight() == null); + if (hasNullValues) { + raiseValidationError("wc-payment-allocation.invalid.allocation.type", + "One or more payment allocation types are invalid or not recognized"); + } + + final List deduced = rules.stream().map(Pair::getRight).distinct().toList(); + if (deduced.size() != expectedCount) { + raiseValidationError("wc-payment-allocation.must.not.have.duplicate.allocation.rule", + "The list of provided payment allocation rules must not contain any duplicates"); + } + + if (!Arrays.equals(IntStream.rangeClosed(1, expectedCount).boxed().toArray(), rules.stream().map(Pair::getLeft).toArray())) { + raiseValidationError("wc-payment-allocation.invalid.order", "The provided orders must be between 1 and " + expectedCount); + } + } + + private boolean hasDuplicateTransactionTypes(final List rules) { + return rules != null && rules.stream().map(WorkingCapitalLoanProductPaymentAllocationRule::getTransactionType).distinct().toList() + .size() != rules.size(); + } + + private void validateAllocationRule(final WorkingCapitalLoanProductPaymentAllocationRule rule) { + if (rule.getTransactionType() == null) { + raiseValidationError("wc-payment-allocation.with.not.valid.transaction.type", + "Payment allocation was provided with a not valid transaction type"); + } + } + + private boolean hasAtLeastOneDefaultPaymentAllocation(final List rules) { + return rules != null && !rules.stream() // + .filter(r -> PaymentAllocationTransactionType.DEFAULT.equals(r.getTransactionType())) // + .toList() // + .isEmpty(); + } + + private boolean hasWCPaymentAllocationRule(final List rules) { + return rules != null && !rules.isEmpty(); + } + + public void raiseValidationError(final String globalisationMessageCode, final String msg) { + throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError(globalisationMessageCode, msg))); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAmortizationType.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAmortizationType.java new file mode 100644 index 00000000000..baef14ed303 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalAmortizationType.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; +import org.springframework.util.StringUtils; + +/** + * Amortization types for Working Capital Loan Product. + */ +@Getter +@RequiredArgsConstructor +public enum WorkingCapitalAmortizationType implements ApiFacingEnum { + + EIR(1, "EIR", "Effective Interest Rate"), // + FLAT(2, "FLAT", "Flat") // + ; + + private final Integer value; + private final String code; + private final String humanReadableName; + + /** + * Resolve enum from its string name (e.g. "EIR", "FLAT"). + */ + public static WorkingCapitalAmortizationType fromString(final String amortizationTypeValue) { + if (!StringUtils.hasText(amortizationTypeValue)) { + return null; + } + + if (amortizationTypeValue.trim().equalsIgnoreCase(EIR.name())) { + return EIR; + } + if (amortizationTypeValue.trim().equalsIgnoreCase(FLAT.name())) { + return FLAT; + } + + return null; + } + + public boolean isFLAT() { + return this.equals(WorkingCapitalAmortizationType.FLAT); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java index 8c454b97e2c..8544302098c 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoan.java @@ -18,6 +18,49 @@ */ package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; -public class WorkingCapitalLoan { +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import jakarta.persistence.Version; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatusConverter; + +@Entity +@Table(name = "m_wc_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "wc_loan_account_no_UNIQUE"), + @UniqueConstraint(columnNames = { "external_id" }, name = "wc_loan_externalid_UNIQUE") }) +@Getter +public class WorkingCapitalLoan extends AbstractAuditableWithUTCDateTimeCustom { + + @Version + int version; + + @Setter() + @Column(name = "account_no", length = 20, unique = true, nullable = false) + private String accountNumber; + + @Setter + @Column(name = "last_closed_business_date") + private LocalDate lastClosedBusinessDate; + + @Setter(AccessLevel.PACKAGE) + @Column(name = "loan_status_id", nullable = false) + @Convert(converter = LoanStatusConverter.class) + private LoanStatus loanStatus; + + @Setter() + @Column(name = "external_id") + private ExternalId externalId; + + @Setter() + @Column(name = "disbursedon_date") + private LocalDate actualDisbursementDate; } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanPeriodFrequencyType.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanPeriodFrequencyType.java new file mode 100644 index 00000000000..298d5233407 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanPeriodFrequencyType.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; +import org.springframework.util.StringUtils; + +/** + * Period frequency types for Working Capital Loan Product. + */ +@Getter +@RequiredArgsConstructor +public enum WorkingCapitalLoanPeriodFrequencyType implements ApiFacingEnum { + + DAYS(1, "DAYS", "Days"), // + MONTHS(2, "MONTHS", "Months"), // + YEARS(3, "YEARS", "Years") // + ; + + private final Integer value; + private final String code; + private final String humanReadableName; + + /** + * Resolve enum from its string name/code (e.g. "DAYS", "MONTHS", "YEARS"). + */ + public static WorkingCapitalLoanPeriodFrequencyType fromString(final String periodFrequencyTypeValue) { + if (!StringUtils.hasText(periodFrequencyTypeValue)) { + return null; + } + + if (periodFrequencyTypeValue.trim().equalsIgnoreCase(DAYS.name())) { + return DAYS; + } + if (periodFrequencyTypeValue.trim().equalsIgnoreCase(MONTHS.name())) { + return MONTHS; + } + if (periodFrequencyTypeValue.trim().equalsIgnoreCase(YEARS.name())) { + return YEARS; + } + + return null; + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProduct.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProduct.java index be69c6990fe..c8c9fd5aba6 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProduct.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProduct.java @@ -18,6 +18,123 @@ */ package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; -public class WorkingCapitalLoanProduct { +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; +import org.apache.fineract.portfolio.fund.domain.Fund; + +/** + * Working Capital Loan Product entity. This is a separate entity from the standard LoanProduct to provide flexibility + * for configuring Working Capital loan products without impacting existing loan products. + */ +@Entity +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "m_wc_loan_product", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_wc_loan_product_name"), + @UniqueConstraint(columnNames = { "external_id" }, name = "unq_wc_loan_product_external_id"), + @UniqueConstraint(columnNames = { "short_name" }, name = "unq_wc_loan_product_short_name") }) +public class WorkingCapitalLoanProduct extends AbstractPersistableCustom { + + // Details category + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "short_name", nullable = false) + private String shortName; + + @Column(name = "external_id", length = 100) + private ExternalId externalId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "fund_id") + private Fund fund; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "delinquency_bucket_classification_id") + private DelinquencyBucket delinquencyBucket; + + @Column(name = "start_date") + private LocalDate startDate; + + @Column(name = "close_date") + private LocalDate closeDate; + + @Column(name = "description") + private String description; + + // Currency (MonetaryCurrency is @Embeddable) + @Embedded + private MonetaryCurrency currency; + + // Core product parameters + @Embedded + private WorkingCapitalLoanProductRelatedDetail relatedDetail; + + // Min/max constraints + @Embedded + private WorkingCapitalLoanProductMinMaxConstraints minMaxConstraints; + + // Payment allocation rules + @OneToMany(cascade = CascadeType.ALL, mappedBy = "wcProduct", orphanRemoval = true, fetch = FetchType.EAGER) + private List paymentAllocationRules = new ArrayList<>(); + + // Configurable attributes + @OneToOne(cascade = CascadeType.ALL, mappedBy = "wcProduct", orphanRemoval = true, fetch = FetchType.EAGER) + private WorkingCapitalLoanProductConfigurableAttributes configurableAttributes; + + public WorkingCapitalLoanProduct(String name, String shortName, ExternalId externalId, Fund fund, DelinquencyBucket delinquencyBucket, + LocalDate startDate, LocalDate closeDate, String description, MonetaryCurrency currency, + WorkingCapitalLoanProductRelatedDetail relatedDetail, WorkingCapitalLoanProductMinMaxConstraints minMaxConstraints, + List paymentAllocationRules, + WorkingCapitalLoanProductConfigurableAttributes configurableAttributes) { + this.name = name; + this.shortName = shortName; + this.externalId = externalId; + this.fund = fund; + this.delinquencyBucket = delinquencyBucket; + this.startDate = startDate; + this.closeDate = closeDate; + this.description = description; + this.currency = currency; + this.relatedDetail = relatedDetail; + this.minMaxConstraints = minMaxConstraints; + this.paymentAllocationRules = paymentAllocationRules; + if (this.paymentAllocationRules != null) { + for (WorkingCapitalLoanProductPaymentAllocationRule rule : this.paymentAllocationRules) { + rule.setWcProduct(this); + } + } + this.configurableAttributes = configurableAttributes; + if (this.configurableAttributes != null) { + this.configurableAttributes.setWcProduct(this); + } + } + + public void updatePaymentAllocationRules(final List newRules) { + if (newRules != null) { + this.paymentAllocationRules.clear(); + this.paymentAllocationRules.addAll(newRules); + } + } } diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java new file mode 100644 index 00000000000..bbb58e59894 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductConfigurableAttributes.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; + +/** + * Configurable attributes for Working Capital Loan Product. Fields that can be overridden during loan creation. + */ +@Entity +@Table(name = "m_wc_loan_product_configurable_attributes") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class WorkingCapitalLoanProductConfigurableAttributes extends AbstractPersistableCustom { + + @OneToOne + @JoinColumn(name = "wc_loan_product_id", nullable = false) + private WorkingCapitalLoanProduct wcProduct; + + @Column(name = "flat_percentage_amount_overridable") + private Boolean flatPercentageAmount; + + @Column(name = "delinquency_bucket_classification_overridable") + private Boolean delinquencyBucketClassification; + + @Column(name = "discount_default_overridable") + private Boolean discountDefault; + + @Column(name = "period_payment_frequency_overridable") + private Boolean periodPaymentFrequency; + + @Column(name = "period_payment_frequency_type_overridable") + private Boolean periodPaymentFrequencyType; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductMinMaxConstraints.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductMinMaxConstraints.java new file mode 100644 index 00000000000..a76b143f4aa --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductMinMaxConstraints.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import java.math.BigDecimal; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * WorkingCapitalLoanProductMinMaxConstraints encapsulates the min/max bounds for principal and period payment rate of a + * {@link WorkingCapitalLoanProduct} (aligned with + * {@link org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinMaxConstraints} by functionality). + */ +@Embeddable +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class WorkingCapitalLoanProductMinMaxConstraints { + + @Column(name = "min_principal_amount", scale = 6, precision = 19) + private BigDecimal minPrincipal; + + @Column(name = "max_principal_amount", scale = 6, precision = 19) + private BigDecimal maxPrincipal; + + @Column(name = "min_period_payment_rate", scale = 6, precision = 19) + private BigDecimal minPeriodPaymentRate; + + @Column(name = "max_period_payment_rate", scale = 6, precision = 19) + private BigDecimal maxPeriodPaymentRate; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductPaymentAllocationRule.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductPaymentAllocationRule.java new file mode 100644 index 00000000000..a451fb9d279 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductPaymentAllocationRule.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; + +@Entity +@Table(name = "m_wc_loan_product_payment_allocation_rule", uniqueConstraints = { @UniqueConstraint(columnNames = { "wc_loan_product_id", + "transaction_type" }, name = "uq_wc_loan_product_payment_allocation_rule") }) +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class WorkingCapitalLoanProductPaymentAllocationRule extends AbstractAuditableWithUTCDateTimeCustom { + + @ManyToOne + @JoinColumn(name = "wc_loan_product_id", nullable = false) + private WorkingCapitalLoanProduct wcProduct; + + @Column(name = "transaction_type", nullable = false) + @Enumerated(EnumType.STRING) + private PaymentAllocationTransactionType transactionType; + + @Convert(converter = WorkingCapitalPaymentAllocationTypeListConverter.class) + @Column(name = "allocation_types", nullable = false) + private List allocationTypes; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductRelatedDetail.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductRelatedDetail.java new file mode 100644 index 00000000000..3b1ca4cc386 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalLoanProductRelatedDetail.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import java.math.BigDecimal; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * WorkingCapitalLoanProductRelatedDetail encapsulates the core product parameters of a + * {@link WorkingCapitalLoanProduct} that define repayment and amortization behaviour (aligned with + * {@link org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail} by functionality). + */ +@Embeddable +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class WorkingCapitalLoanProductRelatedDetail { + + @Enumerated(EnumType.STRING) + @Column(name = "amortization_type", nullable = false) + private WorkingCapitalAmortizationType amortizationType; + + @Column(name = "flat_percentage_amount", scale = 6, precision = 19) + private BigDecimal flatPercentageAmount; + + @Column(name = "npv_day_count", nullable = false) + private Integer npvDayCount; + + @Column(name = "principal_amount", scale = 6, precision = 19, nullable = false) + private BigDecimal principal; + + @Column(name = "period_payment_rate", scale = 6, precision = 19, nullable = false) + private BigDecimal periodPaymentRate; + + @Column(name = "repayment_every", nullable = false) + private Integer repaymentEvery; + + @Enumerated(EnumType.STRING) + @Column(name = "repayment_frequency_enum", nullable = false) + private WorkingCapitalLoanPeriodFrequencyType repaymentFrequencyType; + + @Column(name = "discount", scale = 6, precision = 19) + private BigDecimal discount; +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationType.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationType.java new file mode 100644 index 00000000000..6c5cc318b34 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationType.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; + +/** + * Payment allocation types for Working Capital Loan Product. Only PRINCIPAL, FEE, and PENALTY (no INTEREST). + */ +@Getter +@RequiredArgsConstructor +public enum WorkingCapitalPaymentAllocationType implements ApiFacingEnum { + + PENALTY("PENALTY", "Penalty"), // + FEE("FEE", "Fee"), // + PRINCIPAL("PRINCIPAL", "Principal"); // + + private final String code; + private final String humanReadableName; + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationTypeListConverter.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationTypeListConverter.java new file mode 100644 index 00000000000..6fba1e0f48f --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/domain/WorkingCapitalPaymentAllocationTypeListConverter.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.domain; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.List; +import org.apache.fineract.infrastructure.core.data.GenericEnumListConverter; + +/** + * Converter for WorkingCapitalPaymentAllocationType list to/from comma-separated string. Follows the same pattern as + * PaymentAllocationTypeListConverter in LoanProduct. + */ +@Converter(autoApply = true) +public class WorkingCapitalPaymentAllocationTypeListConverter extends GenericEnumListConverter + implements AttributeConverter, String> { + + @Override + public boolean isUnique() { + return true; + } + + public WorkingCapitalPaymentAllocationTypeListConverter() { + super(WorkingCapitalPaymentAllocationType.class); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateExternalIdException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateExternalIdException.java new file mode 100644 index 00000000000..fdafdcf9a2e --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateExternalIdException.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when a Working Capital Loan Product with the same external ID already exists. + */ +public class WorkingCapitalLoanProductDuplicateExternalIdException extends AbstractPlatformDomainRuleException { + + public WorkingCapitalLoanProductDuplicateExternalIdException(final String externalId) { + super("error.msg.wc.product.duplicate.externalId", + "Working Capital Loan Product with external id '" + externalId + "' already exists", externalId); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateNameException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateNameException.java new file mode 100644 index 00000000000..d296d3a63c0 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateNameException.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when a Working Capital Loan Product with the same name already exists. + */ +public class WorkingCapitalLoanProductDuplicateNameException extends AbstractPlatformDomainRuleException { + + public WorkingCapitalLoanProductDuplicateNameException(final String name) { + super("error.msg.wc.product.duplicate.name", "Working Capital Loan Product with name '" + name + "' already exists", name); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateShortNameException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateShortNameException.java new file mode 100644 index 00000000000..91371b723c1 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductDuplicateShortNameException.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +/** + * A {@link RuntimeException} thrown when a Working Capital Loan Product with the same short name already exists. + */ +public class WorkingCapitalLoanProductDuplicateShortNameException extends AbstractPlatformDomainRuleException { + + public WorkingCapitalLoanProductDuplicateShortNameException(final String shortName) { + super("error.msg.wc.product.duplicate.shortName", "Working Capital Loan Product with short name '" + shortName + "' already exists", + shortName); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductNotFoundException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductNotFoundException.java new file mode 100644 index 00000000000..f1e11dbbf54 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/exception/WorkingCapitalLoanProductNotFoundException.java @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.exception; + +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +/** + * A {@link RuntimeException} thrown when Working Capital Loan Product resources are not found. + */ +public class WorkingCapitalLoanProductNotFoundException extends AbstractPlatformResourceNotFoundException { + + public WorkingCapitalLoanProductNotFoundException(final Long id) { + super("error.msg.wc.product.id.invalid", "Working Capital Loan Product with identifier " + id + " does not exist", id); + } + + public WorkingCapitalLoanProductNotFoundException(final ExternalId externalId) { + super("error.msg.wc.product.id.invalid", + "Working Capital Loan Product with identifier " + externalId.getValue() + " does not exist", externalId.getValue()); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/CreateWorkingCapitalLoanProductCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/CreateWorkingCapitalLoanProductCommandHandler.java new file mode 100644 index 00000000000..ebbd1859ad9 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/CreateWorkingCapitalLoanProductCommandHandler.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.handler; + +import lombok.AllArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.workingcapitalloanproduct.service.WorkingCapitalLoanProductWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@AllArgsConstructor +@CommandType(entity = "WORKINGCAPITALLOANPRODUCT", action = "CREATE") +public class CreateWorkingCapitalLoanProductCommandHandler implements NewCommandSourceHandler { + + private final WorkingCapitalLoanProductWritePlatformService writePlatformService; + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.createWorkingCapitalLoanProduct(command); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/DeleteWorkingCapitalLoanProductCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/DeleteWorkingCapitalLoanProductCommandHandler.java new file mode 100644 index 00000000000..6d942a1ad6a --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/DeleteWorkingCapitalLoanProductCommandHandler.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.handler; + +import lombok.AllArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.workingcapitalloanproduct.service.WorkingCapitalLoanProductWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@AllArgsConstructor +@CommandType(entity = "WORKINGCAPITALLOANPRODUCT", action = "DELETE") +public class DeleteWorkingCapitalLoanProductCommandHandler implements NewCommandSourceHandler { + + private final WorkingCapitalLoanProductWritePlatformService writePlatformService; + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.deleteWorkingCapitalLoanProduct(command.entityId()); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/UpdateWorkingCapitalLoanProductCommandHandler.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/UpdateWorkingCapitalLoanProductCommandHandler.java new file mode 100644 index 00000000000..e63f7bca971 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/handler/UpdateWorkingCapitalLoanProductCommandHandler.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.handler; + +import lombok.AllArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.workingcapitalloanproduct.service.WorkingCapitalLoanProductWritePlatformService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@AllArgsConstructor +@CommandType(entity = "WORKINGCAPITALLOANPRODUCT", action = "UPDATE") +public class UpdateWorkingCapitalLoanProductCommandHandler implements NewCommandSourceHandler { + + private final WorkingCapitalLoanProductWritePlatformService writePlatformService; + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.updateWorkingCapitalLoanProduct(command.entityId(), command); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductBasicDetailsMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductBasicDetailsMapper.java new file mode 100644 index 00000000000..9dd7fdf87dd --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductBasicDetailsMapper.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.mapper; + +import java.util.List; +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class) +public interface WorkingCapitalLoanProductBasicDetailsMapper { + + @Mapping(target = "productType", constant = "working-capital") + @Mapping(target = "currency", source = "currency", qualifiedByName = "currencyData") + LoanProductBasicDetailsData map(WorkingCapitalLoanProduct source); + + List map(List source); + + @Named("currencyData") + default CurrencyData currencyData(final MonetaryCurrency currency) { + return currency.toData(); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java new file mode 100644 index 00000000000..b9cd5c71b1f --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/mapper/WorkingCapitalLoanProductMapper.java @@ -0,0 +1,140 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.mapper; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig; +import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductConfigurableAttributesData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalPaymentAllocationData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductConfigurableAttributes; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductPaymentAllocationRule; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalPaymentAllocationType; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(config = MapstructMapperConfig.class, uses = { DelinquencyBucketMapper.class }) +public interface WorkingCapitalLoanProductMapper { + + @Mapping(target = "fundId", source = "fund.id") + @Mapping(target = "fundName", source = "fund.name") + @Mapping(target = "externalId", source = "externalId", qualifiedByName = "externalIdToString") + @Mapping(target = "status", source = "closeDate", qualifiedByName = "productStatus") + @Mapping(target = "currency", source = "currency", qualifiedByName = "monetaryCurrencyToCurrencyData") + @Mapping(target = "amortizationType", source = "relatedDetail.amortizationType", qualifiedByName = "amortizationToStringEnumOptionData") + @Mapping(target = "flatPercentageAmount", source = "relatedDetail.flatPercentageAmount") + @Mapping(target = "npvDayCount", source = "relatedDetail.npvDayCount") + @Mapping(target = "paymentAllocation", source = "paymentAllocationRules", qualifiedByName = "paymentAllocationRulesToData") + @Mapping(target = "minPrincipal", source = "minMaxConstraints.minPrincipal") + @Mapping(target = "principal", source = "relatedDetail.principal") + @Mapping(target = "maxPrincipal", source = "minMaxConstraints.maxPrincipal") + @Mapping(target = "minPeriodPaymentRate", source = "minMaxConstraints.minPeriodPaymentRate") + @Mapping(target = "periodPaymentRate", source = "relatedDetail.periodPaymentRate") + @Mapping(target = "maxPeriodPaymentRate", source = "minMaxConstraints.maxPeriodPaymentRate") + @Mapping(target = "discount", source = "relatedDetail.discount") + @Mapping(target = "repaymentEvery", source = "relatedDetail.repaymentEvery") + @Mapping(target = "repaymentFrequencyType", source = "relatedDetail.repaymentFrequencyType", qualifiedByName = "periodFrequencyTypeToStringEnumOptionData") + @Mapping(target = "allowAttributeOverrides", source = "configurableAttributes", qualifiedByName = "configurableAttributesToData") + @Mapping(target = "fundOptions", ignore = true) + @Mapping(target = "currencyOptions", ignore = true) + @Mapping(target = "amortizationTypeOptions", ignore = true) + @Mapping(target = "periodFrequencyTypeOptions", ignore = true) + @Mapping(target = "advancedPaymentAllocationTypes", ignore = true) + @Mapping(target = "advancedPaymentAllocationTransactionTypes", ignore = true) + @Mapping(target = "applyTemplate", ignore = true) + @Mapping(target = "delinquencyBucketOptions", ignore = true) + WorkingCapitalLoanProductData toData(WorkingCapitalLoanProduct entity); + + List toDataList(List entities); + + @Named("externalIdToString") + default String externalIdToString(final ExternalId externalId) { + return externalId != null ? externalId.getValue() : null; + } + + @Named("productStatus") + default String productStatus(final LocalDate closeDate) { + return (closeDate != null && DateUtils.isBeforeBusinessDate(closeDate)) ? "loanProduct.inActive" : "loanProduct.active"; + } + + @Named("monetaryCurrencyToCurrencyData") + default CurrencyData monetaryCurrencyToCurrencyData(final MonetaryCurrency currency) { + if (currency == null) { + return null; + } + return new CurrencyData(currency.getCode(), null, currency.getDigitsAfterDecimal(), currency.getInMultiplesOf(), null, null); + } + + @Named("amortizationToStringEnumOptionData") + default StringEnumOptionData amortizationToStringEnumOptionData(final WorkingCapitalAmortizationType amortizationType) { + return amortizationType != null ? amortizationType.getValueAsStringEnumOptionData() : null; + } + + @Named("periodFrequencyTypeToStringEnumOptionData") + default StringEnumOptionData periodFrequencyTypeToStringEnumOptionData( + final WorkingCapitalLoanPeriodFrequencyType periodFrequencyType) { + return periodFrequencyType != null ? periodFrequencyType.getValueAsStringEnumOptionData() : null; + } + + @Named("paymentAllocationRulesToData") + default List paymentAllocationRulesToData( + final List rules) { + if (rules == null || rules.isEmpty()) { + return null; + } + return rules.stream().map(rule -> { + final List paymentAllocationOrder = new ArrayList<>(); + final AtomicInteger counter = new AtomicInteger(1); + for (final WorkingCapitalPaymentAllocationType allocationType : rule.getAllocationTypes()) { + paymentAllocationOrder.add( + new WorkingCapitalPaymentAllocationData.PaymentAllocationOrder(allocationType.name(), counter.getAndIncrement())); + } + return new WorkingCapitalPaymentAllocationData(rule.getTransactionType() != null ? rule.getTransactionType() : null, + paymentAllocationOrder); + }).toList(); + } + + @Named("configurableAttributesToData") + default WorkingCapitalLoanProductConfigurableAttributesData configurableAttributesToData( + final WorkingCapitalLoanProductConfigurableAttributes configurableAttributes) { + if (configurableAttributes == null) { + return null; + } + return WorkingCapitalLoanProductConfigurableAttributesData.builder() // + .flatPercentageAmount(configurableAttributes.getFlatPercentageAmount()) // + .delinquencyBucketClassification(configurableAttributes.getDelinquencyBucketClassification()) // + .discountDefault(configurableAttributes.getDiscountDefault()) // + .periodPaymentFrequency(configurableAttributes.getPeriodPaymentFrequency()) // + .periodPaymentFrequencyType(configurableAttributes.getPeriodPaymentFrequencyType()) // + .build(); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductPaymentAllocationRuleRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductPaymentAllocationRuleRepository.java new file mode 100644 index 00000000000..d22e6bcb8ca --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductPaymentAllocationRuleRepository.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.repository; + +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductPaymentAllocationRule; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface WorkingCapitalLoanProductPaymentAllocationRuleRepository + extends JpaRepository, + JpaSpecificationExecutor { + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductRepository.java new file mode 100644 index 00000000000..c8f647157b8 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanProductRepository.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.repository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface WorkingCapitalLoanProductRepository + extends JpaRepository, JpaSpecificationExecutor { + + boolean existsByExternalId(ExternalId externalId); + + boolean existsByName(String name); + + boolean existsByShortName(String shortName); + + @Query(""" + SELECT wclp FROM WorkingCapitalLoanProduct wclp + LEFT JOIN FETCH wclp.fund + LEFT JOIN FETCH wclp.delinquencyBucket + ORDER BY wclp.name + """) + List findAllWithFund(); + + @Query(""" + SELECT wclp FROM WorkingCapitalLoanProduct wclp + LEFT JOIN FETCH wclp.fund + LEFT JOIN FETCH wclp.delinquencyBucket + LEFT JOIN FETCH wclp.paymentAllocationRules + LEFT JOIN FETCH wclp.configurableAttributes + WHERE wclp.id = :id + """) + Optional findByIdWithDetails(@Param("id") Long id); + + @Query(""" + SELECT wclp FROM WorkingCapitalLoanProduct wclp + LEFT JOIN FETCH wclp.fund + LEFT JOIN FETCH wclp.delinquencyBucket + LEFT JOIN FETCH wclp.paymentAllocationRules + LEFT JOIN FETCH wclp.configurableAttributes + WHERE wclp.externalId = :externalId + """) + Optional findByExternalIdWithDetails(@Param("externalId") ExternalId externalId); + + @Query("select wclp FROM WorkingCapitalLoanProduct wclp where wclp.closeDate is null or wclp.closeDate >= :businessDate") + List fetchActiveWorkingCapitalLoanProducts(LocalDate businessDate); + + // TODO: Check if product is used in any loans (for deletion validation) + // This will be implemented when Working Capital Loan entity is created + // @Query("SELECT CASE WHEN COUNT(l)>0 THEN TRUE ELSE FALSE END FROM WorkingCapitalLoan l WHERE l.wcpProduct.id = + // :productId") + // boolean isProductInUse(@Param("productId") Long productId); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanRepository.java new file mode 100644 index 00000000000..50975d5868b --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/repository/WorkingCapitalLoanRepository.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.repository; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.List; +import org.apache.fineract.cob.data.COBIdAndExternalIdAndAccountNo; +import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoan; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +public interface WorkingCapitalLoanRepository extends JpaRepository, JpaSpecificationExecutor, + CrudRepository { + + @Query("select loan.id from WorkingCapitalLoan loan where loan.id BETWEEN :minAccountId and :maxAccountId and loan.loanStatus in :nonClosedLoanStatuses and :cobBusinessDate = loan.lastClosedBusinessDate") + List findAllLoansByLastClosedBusinessDateNotNullAndMinAndMaxLoanIdAndStatuses(Long minAccountId, Long maxAccountId, + LocalDate cobBusinessDate, Collection nonClosedLoanStatuses); + + @Query("select loan.id from WorkingCapitalLoan loan where loan.id BETWEEN :minAccountId and :maxAccountId and loan.loanStatus in :nonClosedLoanStatuses and (:cobBusinessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)") + List findAllLoansByLastClosedBusinessDateAndMinAndMaxLoanIdAndStatuses(Long minAccountId, Long maxAccountId, + LocalDate cobBusinessDate, Collection nonClosedLoanStatuses); + + @Query("select loan.id, loan.lastClosedBusinessDate from WorkingCapitalLoan loan where loan.id IN :loanIds and loan.loanStatus in :loanStatuses and (loan.lastClosedBusinessDate < :cobBusinessDate or loan.lastClosedBusinessDate is null)") + List findAllLoansBehindOrNullByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate, + @Param("loanIds") List loanIds, @Param("loanStatuses") Collection loanStatuses); + + Long findIdByExternalId(ExternalId externalId); + + @Query("select loan.id, loan.lastClosedBusinessDate from WorkingCapitalLoan loan where loan.id IN :loanIds and loan.loanStatus in :loanStatuses and loan.lastClosedBusinessDate < :cobBusinessDate") + List findAllLoansBehindByLoanIdsAndStatuses(@Param("cobBusinessDate") LocalDate cobBusinessDate, + @Param("loanIds") List loanIds, @Param("loanStatuses") Collection loanStatuses); + + @Query("select loan.id, loan.lastClosedBusinessDate from WorkingCapitalLoan loan where loan.id IN :loanIds and loan.loanStatus in :loanStatuses and loan.lastClosedBusinessDate IS NULL and loan.actualDisbursementDate = :cobBusinessDate") + List findAllLoansBehindOnDisbursementDate(@Param("cobBusinessDate") LocalDate cobBusinessDate, + @Param("loanIds") List loanIds, @Param("loanStatuses") Collection loanStatuses); + + @Query("select loan.id, loan.lastClosedBusinessDate from WorkingCapitalLoan loan where loan.loanStatus in :loanStatuses and loan.lastClosedBusinessDate = (select min(l.lastClosedBusinessDate) from WorkingCapitalLoan l where l" + + ".loanStatus in :loanStatuses and l.lastClosedBusinessDate < :cobBusinessDate)") + List findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate, + @Param("loanStatuses") Collection loanStatuses); + + @Query("select loan.id, loan.externalId, loan.accountNumber from WorkingCapitalLoanAccountLock lock left join WorkingCapitalLoan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate") + List findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidator.java new file mode 100644 index 00000000000..e622f8038a2 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidator.java @@ -0,0 +1,537 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.serialization; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.MathUtil; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAdvancedPaymentAllocationsJsonParser; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateExternalIdException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateNameException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateShortNameException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductRepository; +import org.springframework.stereotype.Component; + +/** + * Validator for Working Capital Loan Product data. + */ +@Component +@RequiredArgsConstructor +public class WorkingCapitalLoanProductDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + private final WorkingCapitalLoanProductRepository repository; + private final WorkingCapitalAdvancedPaymentAllocationsJsonParser advancedPaymentAllocationsJsonParser; + + /** + * The parameters supported for this command. + */ + private static final Set SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("locale", "dateFormat", + WorkingCapitalLoanProductConstants.nameParamName, WorkingCapitalLoanProductConstants.shortNameParamName, + WorkingCapitalLoanProductConstants.descriptionParamName, WorkingCapitalLoanProductConstants.fundIdParamName, + WorkingCapitalLoanProductConstants.startDateParamName, WorkingCapitalLoanProductConstants.closeDateParamName, + WorkingCapitalLoanProductConstants.externalIdParamName, WorkingCapitalLoanProductConstants.currencyCodeParamName, + WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, WorkingCapitalLoanProductConstants.inMultiplesOfParamName, + WorkingCapitalLoanProductConstants.amortizationTypeParamName, WorkingCapitalLoanProductConstants.flatPercentageAmountParamName, + WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, WorkingCapitalLoanProductConstants.npvDayCountParamName, + WorkingCapitalLoanProductConstants.paymentAllocationParamName, WorkingCapitalLoanProductConstants.minPrincipalParamName, + WorkingCapitalLoanProductConstants.principalParamName, WorkingCapitalLoanProductConstants.maxPrincipalParamName, + WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, WorkingCapitalLoanProductConstants.periodPaymentRateParamName, + WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, WorkingCapitalLoanProductConstants.discountParamName, + WorkingCapitalLoanProductConstants.repaymentEveryParamName, WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, + WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)); + + private static final Set SUPPORTED_PAYMENT_ALLOCATION_RULE_PARAMS = new HashSet<>( + Arrays.asList("transactionType", "paymentAllocationOrder")); + private static final Set SUPPORTED_PAYMENT_ALLOCATION_ORDER_PARAMS = new HashSet<>( + Arrays.asList("paymentAllocationRule", "order")); + + public void validateForCreate(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(WorkingCapitalLoanProductConstants.RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + // Validate dates + validateInputDates(element, baseDataValidator); + + // Validate Details category + final String name = validateDetailsFields(element, baseDataValidator, true); + final String shortName = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.shortNameParamName, element); + + // Validate Currency category + validateCurrencyFields(element, baseDataValidator, true); + + // Validate Settings category + validateSettingsFields(element, baseDataValidator, true); + + // Validate payment allocation (required on create – must be provided and non-empty) + if (!this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.paymentAllocationParamName, element)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.paymentAllocationParamName).value(null).notNull(); + } else { + validatePaymentAllocationParameters(element, baseDataValidator); + final JsonCommand command = JsonCommand.fromJsonElement(null, element, this.fromApiJsonHelper); + this.advancedPaymentAllocationsJsonParser.assembleWCPaymentAllocationRules(command); + } + + // Validate Term category + final BigDecimal principal = validateTermFields(element, baseDataValidator, true); + + // Validate min/max ranges + validateMinMaxRanges(element, baseDataValidator, principal); + + // Validate configurable attributes if present + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, element)) { + validateConfigurableAttributes(element, baseDataValidator); + } + + // Throw validation errors if any exist + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + // Check for duplicates + final String externalIdValue = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.externalIdParamName, + element); + if (externalIdValue != null && !externalIdValue.isBlank()) { + final ExternalId externalId = ExternalIdFactory.produce(externalIdValue); + if (this.repository.existsByExternalId(externalId)) { + throw new WorkingCapitalLoanProductDuplicateExternalIdException(externalIdValue); + } + } + + if (this.repository.existsByName(name)) { + throw new WorkingCapitalLoanProductDuplicateNameException(name); + } + + if (this.repository.existsByShortName(shortName)) { + throw new WorkingCapitalLoanProductDuplicateShortNameException(shortName); + } + } + + public void validateForUpdate(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(WorkingCapitalLoanProductConstants.RESOURCE_NAME); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + // Validate dates + validateInputDates(element, baseDataValidator); + + // Validate only fields that are present in the request (partial update support) + validateDetailsFields(element, baseDataValidator, false); + + validateCurrencyFields(element, baseDataValidator, false); + + validateSettingsFields(element, baseDataValidator, false); + + // Validate payment allocation if present + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.paymentAllocationParamName, element)) { + validatePaymentAllocationParameters(element, baseDataValidator); + final JsonCommand command = JsonCommand.fromJsonElement(null, element, this.fromApiJsonHelper); + this.advancedPaymentAllocationsJsonParser.assembleWCPaymentAllocationRules(command); + } + + // Validate Term fields if present + final BigDecimal principal = validateTermFields(element, baseDataValidator, false); + + // Validate configurable attributes if present + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, element)) { + validateConfigurableAttributes(element, baseDataValidator); + } + + // Validate min/max constraints if present + validateMinMaxRanges(element, baseDataValidator, principal); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void validateConfigurableAttributes(final JsonElement element, final DataValidatorBuilder baseDataValidator) { + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, element)) { + final JsonObject allowOverrides = element.getAsJsonObject() + .getAsJsonObject(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName); + if (allowOverrides != null && !allowOverrides.isJsonNull()) { + final Set supportedAttributes = getSupportedConfigurableAttributes(); + + // Check for unsupported parameters + this.fromApiJsonHelper.checkForUnsupportedNestedParameters( + WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, allowOverrides, supportedAttributes); + + // Validate boolean values + for (final String attribute : supportedAttributes) { + if (this.fromApiJsonHelper.parameterExists(attribute, allowOverrides)) { + final Boolean attributeValue = this.fromApiJsonHelper.extractBooleanNamed(attribute, allowOverrides); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName) + .value(attributeValue).notNull().validateForBooleanValue(); + } + } + } + } + } + + private void validatePaymentAllocationParameters(final JsonElement element, final DataValidatorBuilder baseDataValidator) { + if (!element.isJsonObject()) { + return; + } + final JsonElement paymentAllocationElement = element.getAsJsonObject() + .get(WorkingCapitalLoanProductConstants.paymentAllocationParamName); + if (paymentAllocationElement == null || !paymentAllocationElement.isJsonArray()) { + return; + } + final String orderParamPath = WorkingCapitalLoanProductConstants.paymentAllocationParamName + ".paymentAllocationOrder"; + for (final JsonElement ruleEl : paymentAllocationElement.getAsJsonArray()) { + if (ruleEl == null || !ruleEl.isJsonObject()) { + continue; + } + final JsonObject rule = ruleEl.getAsJsonObject(); + this.fromApiJsonHelper.checkForUnsupportedNestedParameters(WorkingCapitalLoanProductConstants.paymentAllocationParamName, rule, + SUPPORTED_PAYMENT_ALLOCATION_RULE_PARAMS); + // Mandatory: transactionType + final String transactionType = this.fromApiJsonHelper.extractStringNamed("transactionType", rule); + baseDataValidator.reset().parameter("paymentAllocation.transactionType").value(transactionType).notBlank(); + // Mandatory: paymentAllocationOrder (must be present and array) + final JsonElement orderEl = rule.get("paymentAllocationOrder"); + if (orderEl == null) { + baseDataValidator.reset().parameter("paymentAllocation.paymentAllocationOrder").value(null).notNull(); + } else if (!orderEl.isJsonArray()) { + baseDataValidator.reset().parameter("paymentAllocation.paymentAllocationOrder").failWithCode("must.be.array", + "paymentAllocationOrder must be an array"); + } else { + for (final JsonElement orderItemEl : orderEl.getAsJsonArray()) { + if (orderItemEl == null || !orderItemEl.isJsonObject()) { + continue; + } + final JsonObject orderItem = orderItemEl.getAsJsonObject(); + this.fromApiJsonHelper.checkForUnsupportedNestedParameters(orderParamPath, orderItem, + SUPPORTED_PAYMENT_ALLOCATION_ORDER_PARAMS); + // Mandatory: paymentAllocationRule, order + final String paymentAllocationRule = this.fromApiJsonHelper.extractStringNamed("paymentAllocationRule", orderItem); + baseDataValidator.reset().parameter(orderParamPath + ".paymentAllocationRule").value(paymentAllocationRule).notBlank(); + final Integer order = this.fromApiJsonHelper.extractIntegerNamed("order", orderItem, Locale.getDefault()); + baseDataValidator.reset().parameter(orderParamPath + ".order").value(order).notNull(); + } + } + } + } + + private Set getSupportedConfigurableAttributes() { + final Set supportedAttributes = new HashSet<>(); + supportedAttributes.add(WorkingCapitalLoanProductConstants.flatPercentageAmountOverridableParamName); + supportedAttributes.add(WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName); + supportedAttributes.add(WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName); + supportedAttributes.add(WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName); + supportedAttributes.add(WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName); + return supportedAttributes; + } + + private String validateDetailsFields(final JsonElement element, final DataValidatorBuilder baseDataValidator, final boolean required) { + final String name; + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.nameParamName, element)) { + name = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.nameParamName, element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.nameParamName).value(name).notBlank() + .notExceedingLengthOf(100); + } else { + name = null; + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.shortNameParamName, element)) { + final String shortName = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.shortNameParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.shortNameParamName).value(shortName).notBlank() + .notExceedingLengthOf(4); + } + + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.descriptionParamName, element)) { + final String description = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.descriptionParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.descriptionParamName).value(description) + .notExceedingLengthOf(500); + } + + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.fundIdParamName, element)) { + final Long fundId = this.fromApiJsonHelper.extractLongNamed(WorkingCapitalLoanProductConstants.fundIdParamName, element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.fundIdParamName).value(fundId).ignoreIfNull() + .integerGreaterThanZero(); + } + + return name; + } + + private void validateCurrencyFields(final JsonElement element, final DataValidatorBuilder baseDataValidator, final boolean required) { + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.currencyCodeParamName, element)) { + final String currencyCode = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.currencyCodeParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.currencyCodeParamName).value(currencyCode).notBlank() + .notExceedingLengthOf(3); + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, element)) { + final Integer decimalPlace = this.fromApiJsonHelper + .extractIntegerNamed(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName).value(decimalPlace) + .notNull().inMinMaxRange(0, 6); + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, element)) { + final Integer currencyInMultiplesOf = this.fromApiJsonHelper + .extractIntegerNamed(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.inMultiplesOfParamName).value(currencyInMultiplesOf) + .notNull().integerZeroOrGreater(); + } + } + + private void validateSettingsFields(final JsonElement element, final DataValidatorBuilder baseDataValidator, final boolean required) { + final String amortizationTypeValue; + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.amortizationTypeParamName, element)) { + amortizationTypeValue = this.fromApiJsonHelper.extractStringNamed(WorkingCapitalLoanProductConstants.amortizationTypeParamName, + element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.amortizationTypeParamName).value(amortizationTypeValue) + .notBlank(); + if (amortizationTypeValue != null && !amortizationTypeValue.isBlank()) { + final WorkingCapitalAmortizationType amortizationType = WorkingCapitalAmortizationType.fromString(amortizationTypeValue); + if (amortizationType == null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.amortizationTypeParamName) + .failWithCode("invalid.amortization.type"); + } else { + // If FLAT is selected, flatPercentageAmount is mandatory + if (amortizationType.isFLAT()) { + final BigDecimal flatPercentageAmount = this.fromApiJsonHelper.extractBigDecimalNamed( + WorkingCapitalLoanProductConstants.flatPercentageAmountParamName, element, new HashSet<>()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName) + .value(flatPercentageAmount).notNull().zeroOrPositiveAmount(); + } + } + } + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.npvDayCountParamName, element)) { + final Integer npvDayCount = this.fromApiJsonHelper.extractIntegerNamed(WorkingCapitalLoanProductConstants.npvDayCountParamName, + element, Locale.getDefault()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.npvDayCountParamName).value(npvDayCount).notNull() + .integerGreaterThanZero(); + } + + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element)) { + final Long delinquencyBucketClassificationId = this.fromApiJsonHelper + .extractLongNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName) + .value(delinquencyBucketClassificationId).ignoreIfNull().integerGreaterThanZero(); + } + + } + + private BigDecimal validateTermFields(final JsonElement element, final DataValidatorBuilder baseDataValidator, final boolean required) { + final BigDecimal principal; + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.principalParamName, element)) { + principal = this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.principalParamName, element, + new HashSet<>()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.principalParamName).value(principal).notNull() + .positiveAmount(); + } else { + principal = null; + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, element)) { + final BigDecimal periodPaymentRateParamName = this.fromApiJsonHelper + .extractBigDecimalNamed(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, element, new HashSet<>()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.periodPaymentRateParamName) + .value(periodPaymentRateParamName).notNull().zeroOrPositiveAmount(); + } + + if (required || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.repaymentEveryParamName, element)) { + final Integer periodPaymentFrequency = this.fromApiJsonHelper + .extractIntegerNamed(WorkingCapitalLoanProductConstants.repaymentEveryParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.repaymentEveryParamName).value(periodPaymentFrequency) + .notNull().integerGreaterThanZero(); + } + + if (required + || this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, element)) { + final String repaymentFrequencyTypeValue = this.fromApiJsonHelper + .extractStringNamed(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, element); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName) + .value(repaymentFrequencyTypeValue).notBlank(); + if (repaymentFrequencyTypeValue != null && !repaymentFrequencyTypeValue.isBlank()) { + final WorkingCapitalLoanPeriodFrequencyType repaymentFrequencyType = WorkingCapitalLoanPeriodFrequencyType + .fromString(repaymentFrequencyTypeValue); + if (repaymentFrequencyType == null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName) + .failWithCode("invalid.period.frequency.type"); + } + } + } + + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.discountParamName, element)) { + final BigDecimal discount = this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.discountParamName, + element, new HashSet<>()); + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.discountParamName).value(discount).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + return principal; + } + + private void validateMinMaxRanges(final JsonElement element, final DataValidatorBuilder baseDataValidator, final BigDecimal principal) { + final BigDecimal minPrincipal = this.fromApiJsonHelper + .parameterExists(WorkingCapitalLoanProductConstants.minPrincipalParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.minPrincipalParamName, element, + new HashSet<>()) + : null; + final BigDecimal maxPrincipal = this.fromApiJsonHelper + .parameterExists(WorkingCapitalLoanProductConstants.maxPrincipalParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.maxPrincipalParamName, element, + new HashSet<>()) + : null; + + // Validate min/max values if provided (as per LoanProduct logic) + if (minPrincipal != null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.minPrincipalParamName).value(minPrincipal).ignoreIfNull() + .positiveAmount(); + } + if (maxPrincipal != null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.maxPrincipalParamName).value(maxPrincipal).ignoreIfNull() + .positiveAmount(); + } + + if (minPrincipal != null && maxPrincipal != null) { + if (MathUtil.isGreaterThan(minPrincipal, maxPrincipal)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.minPrincipalParamName) + .failWithCode("must.be.less.than.or.equal.to.max"); + } + } + if (principal != null && minPrincipal != null) { + if (MathUtil.isLessThan(principal, minPrincipal)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.principalParamName) + .failWithCode("must.be.greater.than.or.equal.to.min"); + } + } + if (principal != null && maxPrincipal != null) { + if (MathUtil.isGreaterThan(principal, maxPrincipal)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.principalParamName) + .failWithCode("must.be.less.than.or.equal.to.max"); + } + } + + final BigDecimal periodPaymentRateMin = this.fromApiJsonHelper + .parameterExists(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, + element, new HashSet<>()) + : null; + + final BigDecimal periodPaymentRateMax = this.fromApiJsonHelper + .parameterExists(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, + element, new HashSet<>()) + : null; + final BigDecimal periodPaymentRate = this.fromApiJsonHelper + .parameterExists(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalNamed(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, + element, new HashSet<>()) + : null; + + // Validate min/max values if provided (as per LoanProduct logic for interest rates) + if (periodPaymentRateMin != null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName) + .value(periodPaymentRateMin).ignoreIfNull().zeroOrPositiveAmount(); + } + if (periodPaymentRateMax != null) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName) + .value(periodPaymentRateMax).ignoreIfNull().zeroOrPositiveAmount(); + } + + if (periodPaymentRateMin != null && periodPaymentRateMax != null) { + if (MathUtil.isGreaterThan(periodPaymentRateMin, periodPaymentRateMax)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName) + .failWithCode("must.be.less.than.or.equal.to.max"); + } + } + if (periodPaymentRate != null && periodPaymentRateMin != null) { + if (MathUtil.isLessThan(periodPaymentRate, periodPaymentRateMin)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.periodPaymentRateParamName) + .failWithCode("must.be.greater.than.or.equal.to.min"); + } + } + if (periodPaymentRate != null && periodPaymentRateMax != null) { + if (MathUtil.isGreaterThan(periodPaymentRate, periodPaymentRateMax)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.periodPaymentRateParamName) + .failWithCode("must.be.less.than.or.equal.to.max"); + } + } + } + + private void validateInputDates(final JsonElement element, final DataValidatorBuilder baseDataValidator) { + if (this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.startDateParamName, element) + && this.fromApiJsonHelper.parameterExists(WorkingCapitalLoanProductConstants.closeDateParamName, element)) { + final LocalDate startDate = this.fromApiJsonHelper.extractLocalDateNamed(WorkingCapitalLoanProductConstants.startDateParamName, + element); + final LocalDate closeDate = this.fromApiJsonHelper.extractLocalDateNamed(WorkingCapitalLoanProductConstants.closeDateParamName, + element); + if (closeDate != null && DateUtils.isBefore(closeDate, startDate)) { + baseDataValidator.reset().parameter(WorkingCapitalLoanProductConstants.closeDateParamName) + .failWithCode("must.be.after.startDate", closeDate.toString(), startDate.toString()); + } + } + } + + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadBasicDetailsServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadBasicDetailsServiceImpl.java new file mode 100644 index 00000000000..1f2e62fd791 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadBasicDetailsServiceImpl.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductBasicDetailsData; +import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadBasicDetailsService; +import org.apache.fineract.portfolio.workingcapitalloanproduct.mapper.WorkingCapitalLoanProductBasicDetailsMapper; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class WorkingCapitalLoanProductReadBasicDetailsServiceImpl implements LoanProductReadBasicDetailsService { + + private final WorkingCapitalLoanProductBasicDetailsMapper workingCapitalLoanProductBasicDetailsMapper; + private final WorkingCapitalLoanProductRepository productRepository; + + @Override + public Collection retrieveProducts() { + return workingCapitalLoanProductBasicDetailsMapper + .map(productRepository.fetchActiveWorkingCapitalLoanProducts(DateUtils.getBusinessLocalDate())); + } + +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformService.java new file mode 100644 index 00000000000..be07492f7ed --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformService.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import java.util.List; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; + +public interface WorkingCapitalLoanProductReadPlatformService { + + List retrieveAllWorkingCapitalLoanProducts(); + + WorkingCapitalLoanProductData retrieveWorkingCapitalLoanProduct(Long productId); + + WorkingCapitalLoanProduct retrieveWorkingCapitalLoanProductByExternalId(ExternalId externalId); + + WorkingCapitalLoanProductData retrieveNewWorkingCapitalLoanProductDetails(); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java new file mode 100644 index 00000000000..7ad6e64ec87 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductReadPlatformServiceImpl.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import java.util.Collection; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.data.StringEnumOptionData; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; +import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData; +import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService; +import org.apache.fineract.portfolio.fund.data.FundData; +import org.apache.fineract.portfolio.fund.service.FundReadPlatformService; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.data.WorkingCapitalLoanProductData; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalPaymentAllocationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.mapper.WorkingCapitalLoanProductMapper; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class WorkingCapitalLoanProductReadPlatformServiceImpl implements WorkingCapitalLoanProductReadPlatformService { + + private final WorkingCapitalLoanProductRepository repository; + private final WorkingCapitalLoanProductMapper mapper; + private final FundReadPlatformService fundReadPlatformService; + private final CurrencyReadPlatformService currencyReadPlatformService; + private final DelinquencyReadPlatformService delinquencyReadPlatformService; + + @Override + public List retrieveAllWorkingCapitalLoanProducts() { + final List products = this.repository.findAllWithFund(); + return this.mapper.toDataList(products); + } + + @Override + public WorkingCapitalLoanProductData retrieveWorkingCapitalLoanProduct(final Long productId) { + final WorkingCapitalLoanProduct product = this.repository.findByIdWithDetails(productId) + .orElseThrow(() -> new WorkingCapitalLoanProductNotFoundException(productId)); + return this.mapper.toData(product); + } + + @Override + public WorkingCapitalLoanProduct retrieveWorkingCapitalLoanProductByExternalId(final ExternalId externalId) { + return this.repository.findByExternalIdWithDetails(externalId) + .orElseThrow(() -> new WorkingCapitalLoanProductNotFoundException(externalId)); + } + + @Override + public WorkingCapitalLoanProductData retrieveNewWorkingCapitalLoanProductDetails() { + final Collection fundOptions = this.fundReadPlatformService.retrieveAllFunds(); + final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); + final List amortizationTypeOptions = ApiFacingEnum + .getValuesAsStringEnumOptionDataList(WorkingCapitalAmortizationType.class); + final List periodFrequencyTypeOptions = ApiFacingEnum + .getValuesAsStringEnumOptionDataList(WorkingCapitalLoanPeriodFrequencyType.class); + final List advancedPaymentAllocationTypes = ApiFacingEnum + .getValuesAsStringEnumOptionDataList(WorkingCapitalPaymentAllocationType.class); + final List advancedPaymentAllocationTransactionTypes = PaymentAllocationTransactionType + .getValuesAsEnumOptionDataList(); + final Collection delinquencyBucketOptions = this.delinquencyReadPlatformService + .retrieveAllDelinquencyBuckets(); + + return WorkingCapitalLoanProductData.builder() // + .fundOptions(fundOptions) // + .currencyOptions(currencyOptions) // + .amortizationTypeOptions(amortizationTypeOptions) // + .periodFrequencyTypeOptions(periodFrequencyTypeOptions) // + .advancedPaymentAllocationTypes(advancedPaymentAllocationTypes) // + .advancedPaymentAllocationTransactionTypes(advancedPaymentAllocationTransactionTypes) // + .delinquencyBucketOptions( + delinquencyBucketOptions != null && !delinquencyBucketOptions.isEmpty() ? delinquencyBucketOptions : null) // + .build(); + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java new file mode 100644 index 00000000000..00fb93d21ef --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductUpdateUtil.java @@ -0,0 +1,204 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import com.google.gson.JsonObject; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductConfigurableAttributes; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductMinMaxConstraints; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductRelatedDetail; +import org.springframework.stereotype.Component; + +/** + * Utility for applying update (JsonCommand) to Working Capital Loan Product and its embedded parts. Keeps JsonCommand + * out of domain entities. + */ +@Component +public class WorkingCapitalLoanProductUpdateUtil { + + /** + * Update currency fields on the product from command. + */ + public Map updateCurrency(final WorkingCapitalLoanProduct product, final JsonCommand command) { + final Map changes = new HashMap<>(); + MonetaryCurrency current = product.getCurrency(); + if (current == null) { + return changes; + } + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.currencyCodeParamName, current.getCode())) { + final String newCurrencyCode = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.currencyCodeParamName); + changes.put(WorkingCapitalLoanProductConstants.currencyCodeParamName, newCurrencyCode); + current = new MonetaryCurrency(newCurrencyCode, current.getDigitsAfterDecimal(), current.getInMultiplesOf()); + product.setCurrency(current); + } + if (command.isChangeInIntegerParameterNamed(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, + current.getDigitsAfterDecimal())) { + final Integer newValue = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName); + changes.put(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, newValue); + current = new MonetaryCurrency(current.getCode(), newValue, current.getInMultiplesOf()); + product.setCurrency(current); + } + if (command.isChangeInIntegerParameterNamed(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, + current.getInMultiplesOf())) { + final Integer newValue = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.inMultiplesOfParamName); + changes.put(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, newValue); + product.setCurrency(new MonetaryCurrency(current.getCode(), current.getDigitsAfterDecimal(), newValue)); + } + return changes; + } + + /** + * Update related detail (core product parameters) from command. + */ + public Map updateRelatedDetail(final WorkingCapitalLoanProductRelatedDetail relatedDetail, final JsonCommand command) { + final Map changes = new HashMap<>(); + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.amortizationTypeParamName, + relatedDetail.getAmortizationType().name())) { + final String newValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.amortizationTypeParamName); + changes.put(WorkingCapitalLoanProductConstants.amortizationTypeParamName, newValue); + relatedDetail.setAmortizationType(WorkingCapitalAmortizationType.fromString(newValue)); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName, + relatedDetail.getFlatPercentageAmount())) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName); + changes.put(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName, newValue); + relatedDetail.setFlatPercentageAmount(newValue); + } + if (command.isChangeInIntegerParameterNamed(WorkingCapitalLoanProductConstants.npvDayCountParamName, + relatedDetail.getNpvDayCount())) { + final Integer newValue = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.npvDayCountParamName); + changes.put(WorkingCapitalLoanProductConstants.npvDayCountParamName, newValue); + relatedDetail.setNpvDayCount(newValue); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.principalParamName, + relatedDetail.getPrincipal())) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.principalParamName); + changes.put(WorkingCapitalLoanProductConstants.principalParamName, newValue); + relatedDetail.setPrincipal(newValue); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, + relatedDetail.getPeriodPaymentRate())) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.periodPaymentRateParamName); + changes.put(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, newValue); + relatedDetail.setPeriodPaymentRate(newValue); + } + if (command.isChangeInIntegerParameterNamed(WorkingCapitalLoanProductConstants.repaymentEveryParamName, + relatedDetail.getRepaymentEvery())) { + final Integer newValue = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.repaymentEveryParamName); + changes.put(WorkingCapitalLoanProductConstants.repaymentEveryParamName, newValue); + relatedDetail.setRepaymentEvery(newValue); + } + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, + relatedDetail.getRepaymentFrequencyType().name())) { + final String newValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName); + changes.put(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, newValue); + relatedDetail.setRepaymentFrequencyType(WorkingCapitalLoanPeriodFrequencyType.fromString(newValue)); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.discountParamName, relatedDetail.getDiscount())) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.discountParamName); + changes.put(WorkingCapitalLoanProductConstants.discountParamName, newValue); + relatedDetail.setDiscount(newValue); + } + return changes; + } + + /** + * Update min/max constraints from command. + */ + public Map updateMinMaxConstraints(final WorkingCapitalLoanProductMinMaxConstraints minMaxConstraints, + final JsonCommand command) { + final Map changes = new HashMap<>(); + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.minPrincipalParamName, + minMaxConstraints.getMinPrincipal())) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.minPrincipalParamName); + changes.put(WorkingCapitalLoanProductConstants.minPrincipalParamName, newValue); + minMaxConstraints.setMinPrincipal(newValue); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.maxPrincipalParamName, + minMaxConstraints.getMaxPrincipal())) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.maxPrincipalParamName); + changes.put(WorkingCapitalLoanProductConstants.maxPrincipalParamName, newValue); + minMaxConstraints.setMaxPrincipal(newValue); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, + minMaxConstraints.getMinPeriodPaymentRate())) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName); + changes.put(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, newValue); + minMaxConstraints.setMinPeriodPaymentRate(newValue); + } + if (command.isChangeInBigDecimalParameterNamed(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, + minMaxConstraints.getMaxPeriodPaymentRate())) { + final BigDecimal newValue = command + .bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName); + changes.put(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, newValue); + minMaxConstraints.setMaxPeriodPaymentRate(newValue); + } + return changes; + } + + /** + * Update configurable attributes from command. + */ + public Map updateConfigurableAttributes(final WorkingCapitalLoanProductConfigurableAttributes config, + final JsonCommand command) { + final Map changes = new HashMap<>(); + if (command.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)) { + final JsonObject allowOverrides = command.parsedJson().getAsJsonObject() + .getAsJsonObject(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName); + if (allowOverrides != null && !allowOverrides.isJsonNull()) { + updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.flatPercentageAmountOverridableParamName, + config::setFlatPercentageAmount, config::getFlatPercentageAmount, changes); + updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName, + config::setDelinquencyBucketClassification, config::getDelinquencyBucketClassification, changes); + updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName, + config::setDiscountDefault, config::getDiscountDefault, changes); + updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName, + config::setPeriodPaymentFrequency, config::getPeriodPaymentFrequency, changes); + updateBooleanField(allowOverrides, WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName, + config::setPeriodPaymentFrequencyType, config::getPeriodPaymentFrequencyType, changes); + } + } + return changes; + } + + private static void updateBooleanField(final JsonObject allowOverrides, final String paramName, final Consumer setter, + final Supplier getter, final Map changes) { + if (allowOverrides.has(paramName)) { + final Boolean newValue = allowOverrides.get(paramName).getAsBoolean(); + if (!Objects.equals(getter.get(), newValue)) { + changes.put(paramName, newValue); + setter.accept(newValue); + } + } + } +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformService.java new file mode 100644 index 00000000000..5bd9869b7b2 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformService.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface WorkingCapitalLoanProductWritePlatformService { + + CommandProcessingResult createWorkingCapitalLoanProduct(JsonCommand command); + + CommandProcessingResult updateWorkingCapitalLoanProduct(Long productId, JsonCommand command); + + CommandProcessingResult deleteWorkingCapitalLoanProduct(Long productId); +} diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java new file mode 100644 index 00000000000..ec5277d83c8 --- /dev/null +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloanproduct/service/WorkingCapitalLoanProductWritePlatformServiceImpl.java @@ -0,0 +1,397 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.service; + +import com.google.gson.JsonObject; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket; +import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; +import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException; +import org.apache.fineract.portfolio.fund.domain.Fund; +import org.apache.fineract.portfolio.fund.domain.FundRepository; +import org.apache.fineract.portfolio.fund.exception.FundNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAdvancedPaymentAllocationsJsonParser; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProduct; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductConfigurableAttributes; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductMinMaxConstraints; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductPaymentAllocationRule; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanProductRelatedDetail; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateExternalIdException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateNameException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductDuplicateShortNameException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.exception.WorkingCapitalLoanProductNotFoundException; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductPaymentAllocationRuleRepository; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductRepository; +import org.apache.fineract.portfolio.workingcapitalloanproduct.serialization.WorkingCapitalLoanProductDataValidator; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class WorkingCapitalLoanProductWritePlatformServiceImpl implements WorkingCapitalLoanProductWritePlatformService { + + private final WorkingCapitalLoanProductDataValidator validator; + private final WorkingCapitalLoanProductRepository repository; + private final WorkingCapitalLoanProductPaymentAllocationRuleRepository paymentAllocationRuleRepository; + private final WorkingCapitalLoanProductUpdateUtil updateUtil; + private final FundRepository fundRepository; + private final DelinquencyBucketRepository delinquencyBucketRepository; + private final WorkingCapitalAdvancedPaymentAllocationsJsonParser advancedPaymentAllocationsJsonParser; + + @Transactional + @Override + public CommandProcessingResult createWorkingCapitalLoanProduct(final JsonCommand command) { + this.validator.validateForCreate(command.json()); + + final Fund fund = findFundByIdIfProvided(command.parameterExists(WorkingCapitalLoanProductConstants.fundIdParamName) + ? command.longValueOfParameterNamed(WorkingCapitalLoanProductConstants.fundIdParamName) + : null); + final DelinquencyBucket delinquencyBucket = findDelinquencyBucketByIdIfProvided( + command.parameterExists(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName) + ? command.longValueOfParameterNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName) + : null); + final List paymentAllocationRules = this.advancedPaymentAllocationsJsonParser + .assembleWCPaymentAllocationRules(command); + final WorkingCapitalLoanProduct product = createProductFromCommand(fund, delinquencyBucket, command, paymentAllocationRules); + + this.repository.saveAndFlush(product); + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(product.getId()) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult updateWorkingCapitalLoanProduct(final Long productId, final JsonCommand command) { + final WorkingCapitalLoanProduct product = this.repository.findById(productId) + .orElseThrow(() -> new WorkingCapitalLoanProductNotFoundException(productId)); + + this.validator.validateForUpdate(command.json()); + + // Check for duplicates before updating (only if values are being changed) + if (command.parameterExists(WorkingCapitalLoanProductConstants.externalIdParamName)) { + final String externalIdValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.externalIdParamName); + if (externalIdValue != null && !externalIdValue.isBlank()) { + final ExternalId externalId = ExternalIdFactory.produce(externalIdValue); + if (this.repository.existsByExternalId(externalId) && !externalId.equals(product.getExternalId())) { + throw new WorkingCapitalLoanProductDuplicateExternalIdException(externalIdValue); + } + } + } + + if (command.parameterExists(WorkingCapitalLoanProductConstants.nameParamName)) { + final String name = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.nameParamName); + if (name != null && !name.isBlank() && this.repository.existsByName(name) && !name.equals(product.getName())) { + throw new WorkingCapitalLoanProductDuplicateNameException(name); + } + } + + if (command.parameterExists(WorkingCapitalLoanProductConstants.shortNameParamName)) { + final String shortName = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.shortNameParamName); + if (shortName != null && !shortName.isBlank() && this.repository.existsByShortName(shortName) + && !shortName.equals(product.getShortName())) { + throw new WorkingCapitalLoanProductDuplicateShortNameException(shortName); + } + } + + final Map changes = updateProductFields(product, command); + + if (!changes.isEmpty()) { + this.repository.saveAndFlush(product); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(productId) // + .with(changes) // + .build(); + } + + @Transactional + @Override + public CommandProcessingResult deleteWorkingCapitalLoanProduct(final Long productId) { + final WorkingCapitalLoanProduct product = this.repository.findById(productId) + .orElseThrow(() -> new WorkingCapitalLoanProductNotFoundException(productId)); + + // TODO: Check if product is used in any loans (when Working Capital Loan entity is created) + // if (isProductInUse(productId)) { + // throw new WorkingCapitalLoanProductCannotBeDeletedException(productId); + // } + + this.repository.delete(product); + + return new CommandProcessingResultBuilder() // + .withEntityId(productId) // + .build(); + } + + private Map updateProductFields(final WorkingCapitalLoanProduct product, final JsonCommand command) { + final Map changes = new HashMap<>(); + + // Update Details category + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.nameParamName, product.getName())) { + final String newValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.nameParamName); + changes.put(WorkingCapitalLoanProductConstants.nameParamName, newValue); + product.setName(newValue); + } + + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.shortNameParamName, product.getShortName())) { + final String newValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.shortNameParamName); + changes.put(WorkingCapitalLoanProductConstants.shortNameParamName, newValue); + product.setShortName(newValue); + } + + if (command.isChangeInStringParameterNamed(WorkingCapitalLoanProductConstants.descriptionParamName, product.getDescription())) { + final String newValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.descriptionParamName); + changes.put(WorkingCapitalLoanProductConstants.descriptionParamName, newValue); + product.setDescription(newValue); + } + + if (command.isChangeInLocalDateParameterNamed(WorkingCapitalLoanProductConstants.startDateParamName, product.getStartDate())) { + final LocalDate newValue = command.localDateValueOfParameterNamed(WorkingCapitalLoanProductConstants.startDateParamName); + changes.put(WorkingCapitalLoanProductConstants.startDateParamName, newValue); + product.setStartDate(newValue); + } + + if (command.isChangeInLocalDateParameterNamed(WorkingCapitalLoanProductConstants.closeDateParamName, product.getCloseDate())) { + final LocalDate newValue = command.localDateValueOfParameterNamed(WorkingCapitalLoanProductConstants.closeDateParamName); + changes.put(WorkingCapitalLoanProductConstants.closeDateParamName, newValue); + product.setCloseDate(newValue); + } + + if (command.isChangeInExternalIdParameterNamed(WorkingCapitalLoanProductConstants.externalIdParamName, product.getExternalId())) { + final String externalIdValue = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.externalIdParamName); + final ExternalId newValue = ExternalIdFactory.produce(externalIdValue); + changes.put(WorkingCapitalLoanProductConstants.externalIdParamName, newValue != null ? newValue.getValue() : null); + product.setExternalId(newValue); + } + + // Update embedded details (via UpdateUtil; JsonCommand does not belong in entities) + if (product.getCurrency() != null) { + changes.putAll(updateUtil.updateCurrency(product, command)); + } + if (product.getRelatedDetail() != null) { + changes.putAll(updateUtil.updateRelatedDetail(product.getRelatedDetail(), command)); + } + if (product.getMinMaxConstraints() != null) { + changes.putAll(updateUtil.updateMinMaxConstraints(product.getMinMaxConstraints(), command)); + } + + // Update fund if changed + final Long existingFundId = product.getFund() != null ? product.getFund().getId() : null; + if (command.isChangeInLongParameterNamed(WorkingCapitalLoanProductConstants.fundIdParamName, existingFundId)) { + final Long fundId = command.longValueOfParameterNamed(WorkingCapitalLoanProductConstants.fundIdParamName); + final Fund fund = findFundByIdIfProvided(fundId); + product.setFund(fund); + changes.put(WorkingCapitalLoanProductConstants.fundIdParamName, fundId); + } + + // Update delinquency bucket if changed + final Long existingDelinquencyBucketId = product.getDelinquencyBucket() != null ? product.getDelinquencyBucket().getId() : null; + if (command.isChangeInLongParameterNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, + existingDelinquencyBucketId)) { + final Long delinquencyBucketId = command + .longValueOfParameterNamed(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName); + final DelinquencyBucket delinquencyBucket = findDelinquencyBucketByIdIfProvided(delinquencyBucketId); + product.setDelinquencyBucket(delinquencyBucket); + changes.put(WorkingCapitalLoanProductConstants.delinquencyBucketIdParamName, delinquencyBucketId); + } + + // Update payment allocation rules if changed + if (command.parameterExists(WorkingCapitalLoanProductConstants.paymentAllocationParamName)) { + final List newRules = this.advancedPaymentAllocationsJsonParser + .assembleWCPaymentAllocationRules(command); + if (newRules != null) { + newRules.forEach(rule -> rule.setWcProduct(product)); + paymentAllocationRuleRepository.deleteAll(product.getPaymentAllocationRules()); + product.updatePaymentAllocationRules(newRules); + changes.put(WorkingCapitalLoanProductConstants.paymentAllocationParamName, + command.jsonFragment(WorkingCapitalLoanProductConstants.paymentAllocationParamName)); + } + } + + // Update configurable attributes if changed + if (command.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)) { + if (product.getConfigurableAttributes() == null) { + // Create new configurable attributes if they don't exist + final WorkingCapitalLoanProductConfigurableAttributes configurableAttributes = createConfigurableAttributesFromCommand( + command); + configurableAttributes.setWcProduct(product); + product.setConfigurableAttributes(configurableAttributes); + changes.put(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, + command.jsonFragment(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)); + } else { + // Update existing configurable attributes + final Map configChanges = updateUtil.updateConfigurableAttributes(product.getConfigurableAttributes(), + command); + if (!configChanges.isEmpty()) { + changes.put(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, + command.jsonFragment(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)); + } + } + } + + return changes; + } + + private WorkingCapitalLoanProduct createProductFromCommand(final Fund fund, final DelinquencyBucket delinquencyBucket, + final JsonCommand command, final List paymentAllocationRules) { + // Details category + final String name = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.nameParamName); + final String shortName = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.shortNameParamName); + final String description = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.descriptionParamName); + final LocalDate startDate = command.localDateValueOfParameterNamed(WorkingCapitalLoanProductConstants.startDateParamName); + final LocalDate closeDate = command.localDateValueOfParameterNamed(WorkingCapitalLoanProductConstants.closeDateParamName); + final ExternalId externalId = command.parameterExists(WorkingCapitalLoanProductConstants.externalIdParamName) + ? ExternalIdFactory.produce(command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.externalIdParamName)) + : null; + + // Currency category + final String currencyCode = command.stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.currencyCodeParamName); + final Integer decimalPlace = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName); + final Integer currencyInMultiplesOf = command + .integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.inMultiplesOfParamName); + final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, decimalPlace, currencyInMultiplesOf); + + // Related detail (core product parameters) + final String amortizationTypeValue = command + .stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.amortizationTypeParamName); + final WorkingCapitalAmortizationType amortizationType = WorkingCapitalAmortizationType.fromString(amortizationTypeValue); + final BigDecimal flatPercentageAmount = command.parameterExists(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.flatPercentageAmountParamName) + : null; + final Integer npvDayCount = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.npvDayCountParamName); + final BigDecimal principal = command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.principalParamName); + final BigDecimal periodPaymentRate = command + .bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.periodPaymentRateParamName); + final Integer repaymentEvery = command.integerValueOfParameterNamed(WorkingCapitalLoanProductConstants.repaymentEveryParamName); + final String repaymentFrequencyTypeValue = command + .stringValueOfParameterNamed(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName); + final WorkingCapitalLoanPeriodFrequencyType repaymentFrequencyType = WorkingCapitalLoanPeriodFrequencyType + .fromString(repaymentFrequencyTypeValue); + final BigDecimal discount = command.parameterExists(WorkingCapitalLoanProductConstants.discountParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.discountParamName) + : null; + final WorkingCapitalLoanProductRelatedDetail relatedDetail = new WorkingCapitalLoanProductRelatedDetail(amortizationType, + flatPercentageAmount, npvDayCount, principal, periodPaymentRate, repaymentEvery, repaymentFrequencyType, discount); + + // Min/max constraints + final BigDecimal minPrincipal = command.parameterExists(WorkingCapitalLoanProductConstants.minPrincipalParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.minPrincipalParamName) + : null; + final BigDecimal maxPrincipal = command.parameterExists(WorkingCapitalLoanProductConstants.maxPrincipalParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.maxPrincipalParamName) + : null; + final BigDecimal minPeriodPaymentRate = command.parameterExists(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName) + : null; + final BigDecimal maxPeriodPaymentRate = command.parameterExists(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName) + ? command.bigDecimalValueOfParameterNamed(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName) + : null; + final WorkingCapitalLoanProductMinMaxConstraints minMaxConstraints = new WorkingCapitalLoanProductMinMaxConstraints(minPrincipal, + maxPrincipal, minPeriodPaymentRate, maxPeriodPaymentRate); + + // Configurable attributes + final WorkingCapitalLoanProductConfigurableAttributes configurableAttributes = createConfigurableAttributesFromCommand(command); + + return new WorkingCapitalLoanProduct(name, shortName, externalId, fund, delinquencyBucket, startDate, closeDate, description, + currency, relatedDetail, minMaxConstraints, paymentAllocationRules, configurableAttributes); + } + + private WorkingCapitalLoanProductConfigurableAttributes createConfigurableAttributesFromCommand(final JsonCommand command) { + Boolean flatPercentageAmount = null; + Boolean delinquencyBucketClassification = null; + Boolean discountDefault = null; + Boolean periodPaymentFrequency = null; + Boolean periodPaymentFrequencyType = null; + + if (command.parameterExists(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName)) { + final JsonObject allowOverrides = command.parsedJson().getAsJsonObject() + .getAsJsonObject(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName); + if (allowOverrides != null && !allowOverrides.isJsonNull()) { + if (allowOverrides.has(WorkingCapitalLoanProductConstants.flatPercentageAmountOverridableParamName) + && !allowOverrides.get(WorkingCapitalLoanProductConstants.flatPercentageAmountOverridableParamName).isJsonNull()) { + flatPercentageAmount = allowOverrides.get(WorkingCapitalLoanProductConstants.flatPercentageAmountOverridableParamName) + .getAsBoolean(); + } + if (allowOverrides.has(WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName) + && !allowOverrides.get(WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName) + .isJsonNull()) { + delinquencyBucketClassification = allowOverrides + .get(WorkingCapitalLoanProductConstants.delinquencyBucketClassificationOverridableParamName).getAsBoolean(); + } + if (allowOverrides.has(WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName) + && !allowOverrides.get(WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName).isJsonNull()) { + discountDefault = allowOverrides.get(WorkingCapitalLoanProductConstants.discountDefaultOverridableParamName) + .getAsBoolean(); + } + if (allowOverrides.has(WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName) && !allowOverrides + .get(WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName).isJsonNull()) { + periodPaymentFrequency = allowOverrides + .get(WorkingCapitalLoanProductConstants.periodPaymentFrequencyOverridableParamName).getAsBoolean(); + } + if (allowOverrides.has(WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName) && !allowOverrides + .get(WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName).isJsonNull()) { + periodPaymentFrequencyType = allowOverrides + .get(WorkingCapitalLoanProductConstants.periodPaymentFrequencyTypeOverridableParamName).getAsBoolean(); + } + } + } + + final WorkingCapitalLoanProductConfigurableAttributes configurableAttributes = new WorkingCapitalLoanProductConfigurableAttributes(); + configurableAttributes.setFlatPercentageAmount(flatPercentageAmount); + configurableAttributes.setDelinquencyBucketClassification(delinquencyBucketClassification); + configurableAttributes.setDiscountDefault(discountDefault); + configurableAttributes.setPeriodPaymentFrequency(periodPaymentFrequency); + configurableAttributes.setPeriodPaymentFrequencyType(periodPaymentFrequencyType); + return configurableAttributes; + } + + private Fund findFundByIdIfProvided(final Long fundId) { + if (fundId == null) { + return null; + } + return this.fundRepository.findById(fundId).orElseThrow(() -> new FundNotFoundException(fundId)); + } + + private DelinquencyBucket findDelinquencyBucketByIdIfProvided(final Long delinquencyBucketId) { + if (delinquencyBucketId == null) { + return null; + } + return this.delinquencyBucketRepository.findById(delinquencyBucketId) + .orElseThrow(() -> DelinquencyBucketNotFoundException.notFound(delinquencyBucketId)); + } + +} diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml index 6bc107ecab9..247d241fe5f 100644 --- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml @@ -22,5 +22,8 @@ - + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_loan_product.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_loan_product.xml new file mode 100644 index 00000000000..fa7c1382362 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0001_loan_product.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0002_wc_loan_schema.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0002_wc_loan_schema.xml new file mode 100644 index 00000000000..a6c104e21cf --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0002_wc_loan_schema.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0003_working_capital_loan_cob.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0003_working_capital_loan_cob.xml new file mode 100644 index 00000000000..e0d7cc18b10 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0003_working_capital_loan_cob.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0004_extend_working_capital_loan_entity.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0004_extend_working_capital_loan_entity.xml new file mode 100644 index 00000000000..6a40a4d85f5 --- /dev/null +++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0004_extend_working_capital_loan_entity.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml b/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml index 3ee3dd86491..155b665babc 100644 --- a/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml +++ b/fineract-working-capital-loan/src/main/resources/jpa/static-weaving/module/fineract-working-capital-loan/persistence.xml @@ -31,8 +31,11 @@ org.eclipse.persistence.jpa.PersistenceProvider + + + false diff --git a/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidatorTest.java b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidatorTest.java new file mode 100644 index 00000000000..f95e2577810 --- /dev/null +++ b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloanproduct/serialization/WorkingCapitalLoanProductDataValidatorTest.java @@ -0,0 +1,352 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.workingcapitalloanproduct.serialization; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.ZoneId; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.workingcapitalloanproduct.WorkingCapitalLoanProductConstants; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAdvancedPaymentAllocationsJsonParser; +import org.apache.fineract.portfolio.workingcapitalloanproduct.repository.WorkingCapitalLoanProductRepository; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class WorkingCapitalLoanProductDataValidatorTest { + + @Mock + private WorkingCapitalLoanProductRepository repository; + @Mock + private WorkingCapitalAdvancedPaymentAllocationsJsonParser advancedPaymentAllocationsJsonParser; + private WorkingCapitalLoanProductDataValidator validator; + + @BeforeEach + void setUp() { + final FromJsonHelper fromApiJsonHelper = new FromJsonHelper(); + validator = new WorkingCapitalLoanProductDataValidator(fromApiJsonHelper, repository, advancedPaymentAllocationsJsonParser); + } + + @Test + void testValidateForCreate_WithValidData_ShouldNotThrowException() { + // Given + final String json = createValidJson(); + + // When & Then + assertDoesNotThrow(() -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithEmptyJson_ShouldThrowException() { + // Given + final String json = ""; + + // When & Then + assertThrows(InvalidJsonException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithNullJson_ShouldThrowException() { + // Given + final String json = null; + + // When & Then + assertThrows(InvalidJsonException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithMissingName_ShouldThrowException() { + // Given + final String json = createJsonWithoutField(WorkingCapitalLoanProductConstants.nameParamName); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithMissingShortName_ShouldThrowException() { + // Given + final String json = createJsonWithoutField(WorkingCapitalLoanProductConstants.shortNameParamName); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithMissingCurrencyCode_ShouldThrowException() { + // Given + final String json = createJsonWithoutField(WorkingCapitalLoanProductConstants.currencyCodeParamName); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithInvalidAmortization_ShouldThrowException() { + // Given + final String json = createJsonWithField(WorkingCapitalLoanProductConstants.amortizationTypeParamName, "INVALID"); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithFlatAmortizationAndMissingFlatPercentageAmount_ShouldThrowException() { + // Given + final String json = createJsonWithField(WorkingCapitalLoanProductConstants.amortizationTypeParamName, "FLAT"); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithMinGreaterThanMaxPrincipalAmount_ShouldThrowException() { + // Given + final String json = createJsonWithPrincipalAmounts(BigDecimal.valueOf(1000), BigDecimal.valueOf(500), BigDecimal.valueOf(2000)); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithMinGreaterThanMaxPeriodPaymentRate_ShouldThrowException() { + // Given + final String json = createJsonWithPeriodPaymentRates(BigDecimal.valueOf(2.0), BigDecimal.valueOf(1.0), BigDecimal.valueOf(3.0)); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForUpdate_WithValidData_ShouldNotThrowException() { + // Given + final String json = createValidJson(); + + // When & Then + assertDoesNotThrow(() -> validator.validateForUpdate(json)); + } + + @Test + void testValidateForUpdate_WithEmptyJson_ShouldThrowException() { + // Given + final String json = ""; + + // When & Then + assertThrows(InvalidJsonException.class, () -> validator.validateForUpdate(json)); + } + + @Test + void testValidateForUpdate_WithInvalidDateRange_ShouldThrowException() { + // Given + final LocalDate startDate = LocalDate.now(ZoneId.systemDefault()).plusDays(10); + final LocalDate closeDate = LocalDate.now(ZoneId.systemDefault()); + final String json = createJsonWithDates(startDate, closeDate); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForUpdate(json)); + } + + @Test + void testValidateConfigurableAttributes_WithUnsupportedAttribute_ShouldThrowException() { + // Given + final String json = createJsonWithUnsupportedConfigurableAttribute(); + + // When & Then + assertThrows(UnsupportedParameterException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithDecimalPlaceZero_ShouldNotThrowException() { + // Given - decimalPlace can be 0 (as per LoanProduct logic: inMinMaxRange(0, 6)) + final String json = createJsonWithDecimalPlace(0); + + // When & Then + assertDoesNotThrow(() -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithDecimalPlaceOutOfRange_ShouldThrowException() { + // Given - decimalPlace must be in range 0-6 + final String json = createJsonWithDecimalPlace(7); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithDecimalPlaceNegative_ShouldThrowException() { + // Given - decimalPlace must be >= 0 + final String json = createJsonWithDecimalPlace(-1); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithCurrencyInMultiplesOfZero_ShouldNotThrowException() { + // Given - currencyInMultiplesOf can be 0 (as per LoanProduct logic: integerZeroOrGreater()) + final String json = createJsonWithCurrencyInMultiplesOf(0); + + // When & Then + assertDoesNotThrow(() -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithCurrencyInMultiplesOfNegative_ShouldThrowException() { + // Given - currencyInMultiplesOf must be >= 0 + final String json = createJsonWithCurrencyInMultiplesOf(-1); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + @Test + void testValidateForCreate_WithCurrencyCodeExceedingLength_ShouldThrowException() { + // Given - currencyCode must not exceed 3 characters (as per LoanProduct logic) + final String json = createJsonWithCurrencyCode("USDD"); + + // When & Then + assertThrows(PlatformApiDataValidationException.class, () -> validator.validateForCreate(json)); + } + + // Helper methods + + private JsonObject createBaseJsonObject() { + final JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.nameParamName, "Test WC Product"); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.shortNameParamName, "TWCP"); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.currencyCodeParamName, "USD"); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, 2); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, 1); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.amortizationTypeParamName, "EIR"); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.npvDayCountParamName, 360); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.principalParamName, 1000); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, 1.0); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.repaymentEveryParamName, 30); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.repaymentFrequencyTypeParamName, "DAYS"); + jsonObject.add(WorkingCapitalLoanProductConstants.paymentAllocationParamName, createDefaultPaymentAllocationJson()); + return jsonObject; + } + + private JsonArray createDefaultPaymentAllocationJson() { + final JsonArray paymentAllocation = new JsonArray(); + final JsonObject rule = new JsonObject(); + rule.addProperty("transactionType", "DEFAULT"); + final JsonArray order = new JsonArray(); + order.add(createPaymentAllocationOrderItem("PENALTY", 1)); + order.add(createPaymentAllocationOrderItem("FEE", 2)); + order.add(createPaymentAllocationOrderItem("PRINCIPAL", 3)); + rule.add("paymentAllocationOrder", order); + paymentAllocation.add(rule); + return paymentAllocation; + } + + private JsonObject createPaymentAllocationOrderItem(String paymentAllocationRule, int order) { + final JsonObject item = new JsonObject(); + item.addProperty("paymentAllocationRule", paymentAllocationRule); + item.addProperty("order", order); + return item; + } + + private String toJsonAndSetupMocks(final JsonObject jsonObject) { + return jsonObject.toString(); + } + + private String createValidJson() { + return toJsonAndSetupMocks(createBaseJsonObject()); + } + + private String createJsonWithoutField(final String fieldName) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.remove(fieldName); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithField(final String fieldName, final String value) { + final JsonObject jsonObject = createBaseJsonObject(); + if (fieldName.equals(WorkingCapitalLoanProductConstants.amortizationTypeParamName) && value.equals("FLAT")) { + jsonObject.addProperty(WorkingCapitalLoanProductConstants.amortizationTypeParamName, "FLAT"); + } + jsonObject.addProperty(fieldName, value); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithPrincipalAmounts(final BigDecimal min, final BigDecimal defaultVal, final BigDecimal max) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.minPrincipalParamName, min); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.principalParamName, defaultVal); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.maxPrincipalParamName, max); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithPeriodPaymentRates(final BigDecimal min, final BigDecimal defaultVal, final BigDecimal max) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.minPeriodPaymentRateParamName, min); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.periodPaymentRateParamName, defaultVal); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.maxPeriodPaymentRateParamName, max); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithDates(final LocalDate startDate, final LocalDate closeDate) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.startDateParamName, startDate.toString()); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.closeDateParamName, closeDate.toString()); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithUnsupportedConfigurableAttribute() { + final JsonObject jsonObject = createBaseJsonObject(); + final JsonObject allowOverrides = new JsonObject(); + allowOverrides.addProperty("unsupportedAttribute", true); + jsonObject.add(WorkingCapitalLoanProductConstants.allowAttributeOverridesParamName, allowOverrides); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithDecimalPlace(final Integer decimalPlace) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.digitsAfterDecimalParamName, decimalPlace); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithCurrencyInMultiplesOf(final Integer currencyInMultiplesOf) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.inMultiplesOfParamName, currencyInMultiplesOf); + return toJsonAndSetupMocks(jsonObject); + } + + private String createJsonWithCurrencyCode(final String currencyCode) { + final JsonObject jsonObject = createBaseJsonObject(); + jsonObject.addProperty(WorkingCapitalLoanProductConstants.currencyCodeParamName, currencyCode); + return toJsonAndSetupMocks(jsonObject); + } +} diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index 3aed9915fa3..2dd6df4470d 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -35,6 +35,8 @@ configurations { } dependencies { driver 'com.mysql:mysql-connector-j' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.junit.jupiter:junit-jupiter-api' } cargo { diff --git a/integration-tests/dependencies.gradle b/integration-tests/dependencies.gradle index 0dc39501c69..6f5a7d412d8 100644 --- a/integration-tests/dependencies.gradle +++ b/integration-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.47@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.49@zip' def providerMainOutput = project(':fineract-provider').extensions.getByType(SourceSetContainer).named('main').get().output testImplementation( providerMainOutput, project(path: ':fineract-core', configuration: 'runtimeElements'), diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 140ecd58807..48b8b7ed82a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -278,6 +278,11 @@ public void uc2() { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 100.0, 25.0, 0.0); assertTrue(loanDetails.getStatus().getOverpaid()); + + // Loan Repayment (after) Overpaid + GetLoansLoanIdTransactionsTemplateResponse transactionAfter = loanTransactionHelper + .retrieveTransactionTemplate(loanResponse.getLoanId(), "repayment", DATETIME_PATTERN, "15 February 2023", LOCALE); + assertNotNull(transactionAfter); }); } // UC3: Overpayment2 @@ -345,6 +350,11 @@ public void uc3() { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 100.0, 25.0, 0.0); assertTrue(loanDetails.getStatus().getOverpaid()); + + // Loan Repayment (after) Overpaid + GetLoansLoanIdTransactionsTemplateResponse transactionAfter = loanTransactionHelper + .retrieveTransactionTemplate(loanResponse.getLoanId(), "repayment", DATETIME_PATTERN, "15 February 2023", LOCALE); + assertNotNull(transactionAfter); }); } // UC4: Delinquent balance diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AuditIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AuditIntegrationTest.java index 5c17dba4d6c..e95becaaedc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AuditIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AuditIntegrationTest.java @@ -36,6 +36,7 @@ import org.apache.fineract.integrationtests.common.AuditHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.OfficeHelper; +import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,6 +52,7 @@ public class AuditIntegrationTest { private RequestSpecification requestSpec; private ClientHelper clientHelper; private AuditHelper auditHelper; + private SchedulerJobHelper schedulerJobHelper; private static final SecureRandom rand = new SecureRandom(); /** @@ -65,6 +67,7 @@ public void setup() { this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.auditHelper = new AuditHelper(this.requestSpec, this.responseSpec); this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec); } @Test @@ -157,4 +160,19 @@ public void checkIfOrderBySupported() { } + @SuppressWarnings("unchecked") + @Test + public void executeSchedulerJobShouldCreateAuditEntry() { + // given + int jobId = schedulerJobHelper.getSchedulerJobIdByShortName("SA_AANF"); + List> auditsRecievedInitial = auditHelper.getAuditDetails(jobId, "EXECUTEJOB", "SCHEDULER"); + + // when + schedulerJobHelper.runSchedulerJob(jobId); + + // then + List> auditsRecieved = auditHelper.getAuditDetails(jobId, "EXECUTEJOB", "SCHEDULER"); + auditHelper.verifyMultipleAuditsOnserver(auditsRecievedInitial, auditsRecieved, jobId, "EXECUTEJOB", "SCHEDULER"); + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index b5b021735ba..9c3dff8fc7d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -84,7 +84,6 @@ import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.models.PostRolesRequest; import org.apache.fineract.client.models.PostUsersRequest; -import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; import org.apache.fineract.client.models.PutLoansApprovedAmountRequest; import org.apache.fineract.client.models.PutLoansApprovedAmountResponse; @@ -96,13 +95,10 @@ import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.client.util.Calls; import org.apache.fineract.client.util.FineractClient; -import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse; import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.common.BatchHelper; -import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; -import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; @@ -138,9 +134,6 @@ @ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class }) public abstract class BaseLoanIntegrationTest extends IntegrationTest { - protected static final String DATETIME_PATTERN = "dd MMMM yyyy"; - protected static final String LOCALE = "en"; - static { Utils.initializeRESTAssured(); } @@ -184,9 +177,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest { protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); protected final LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(requestSpec, createResponseSpecification(Matchers.is(202))); - protected BusinessDateHelper businessDateHelper = new BusinessDateHelper(); protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); - protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper(); protected final CodeHelper codeHelper = new CodeHelper(); protected final ChargesHelper chargesHelper = new ChargesHelper(); protected final ExternalEventHelper externalEventHelper = new ExternalEventHelper(); @@ -374,7 +365,7 @@ public T performPermissionTestForRequest(final String permission, Function { errorLoanTransactionHelper.disburseLoan((long) loanID, @@ -6421,7 +6421,7 @@ public void chargeOff() { .locale("en").dateFormat(DATETIME_PATTERN)); }); assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("error.msg.loan.disbursal.not.allowed.on.charged.off")); + assertTrue(exception.getMessage().contains("amount.can't.be.greater.than.maximum.applied.loan.amount.calculation")); LOAN_TRANSACTION_HELPER.makeLoanRepayment((long) loanID, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN) .transactionDate("07 September 2022").locale("en").transactionAmount(5000.0)); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauConfigurationTest.java index 9c312921a9c..c1e1c3039ae 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauConfigurationTest.java @@ -18,16 +18,11 @@ */ package org.apache.fineract.integrationtests; -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.builder.ResponseSpecBuilder; -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.apache.fineract.integrationtests.common.CreditBureauConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; -import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,34 +30,22 @@ public class CreditBureauConfigurationTest { private static final Logger LOG = LoggerFactory.getLogger(CreditBureauConfigurationTest.class); - private ResponseSpecification responseSpec; - private RequestSpecification requestSpec; - private LoanTransactionHelper loanTransactionHelper; - - private static final String NO_ACCOUNTING = "1"; - - static final int MYTHREADS = 30; - - @BeforeEach - public void setup() { - Utils.initializeRESTAssured(); - this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); - this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - } @Test public void creditBureauConfigurationTest() { // create creditBureauConfiguration - final Integer configurationId = CreditBureauConfigurationHelper.createCreditBureauConfiguration(this.requestSpec, this.responseSpec, - Utils.randomStringGenerator("testConfigKey_", 5)); + String createResponse = CreditBureauConfigurationHelper.createCreditBureauConfiguration(1L, + Utils.randomStringGenerator("testConfigKey_", 5), "testConfigKeyValue", "description"); + JsonObject createJson = JsonParser.parseString(createResponse).getAsJsonObject(); + Long configurationId = createJson.get("resourceId").getAsLong(); Assertions.assertNotNull(configurationId); // update creditBureauConfiguration - final String updateconfiguration = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, configurationId); + String updateResponse = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(configurationId, null, + "updateConfigKeyValue"); + String updateconfiguration = JsonParser.parseString(updateResponse).getAsJsonObject().get("changes").getAsJsonObject().get("value") + .getAsString(); Assertions.assertEquals("updateConfigKeyValue", updateconfiguration); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauTest.java index 49872176e20..25990ff4099 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CreditBureauTest.java @@ -27,11 +27,7 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.google.gson.Gson; import com.google.gson.JsonParser; -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.builder.ResponseSpecBuilder; -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; +import com.google.gson.reflect.TypeToken; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatterBuilder; @@ -52,8 +48,6 @@ public class CreditBureauTest { private static final Logger LOG = LoggerFactory.getLogger(CreditBureauTest.class); - private ResponseSpecification responseSpec; - private RequestSpecification requestSpec; private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -63,53 +57,47 @@ public class CreditBureauTest { @BeforeEach public void setup() { Utils.initializeRESTAssured(); - this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); configureCreditBureauService(); } private void configureCreditBureauService() { - Object organisations = CreditBureauConfigurationHelper.getOrganizationCreditBureauConfiguration(this.requestSpec, - this.responseSpec); + String organisations = CreditBureauConfigurationHelper.getOrganisationCreditBureauConfiguration(); - if (new Gson().fromJson(String.valueOf(organisations), List.class).isEmpty()) { - CreditBureauConfigurationHelper.addOrganisationCreditBureau(this.requestSpec, this.responseSpec, "1", "SAMPLE_ALIAS", true); + if (new Gson().fromJson(organisations, List.class).isEmpty()) { + CreditBureauConfigurationHelper.addOrganisationCreditBureau(1L, "SAMPLE_ALIAS", true); } else { - CreditBureauConfigurationHelper.updateOrganisationCreditBureau(this.requestSpec, this.responseSpec, "1", true); + CreditBureauConfigurationHelper.updateOrganisationCreditBureau("1", true); } - List> configurations = CreditBureauConfigurationHelper.getCreditBureauConfiguration(requestSpec, responseSpec, - "1"); + String configJson = CreditBureauConfigurationHelper.getCreditBureauConfiguration(1L); + List> configurations = new Gson().fromJson(configJson, new TypeToken>>() {}.getType()); Assertions.assertNotNull(configurations); - Map currentConfiguration = configurations.stream().collect(Collectors - .toMap(k -> String.valueOf(k.get("configurationKey")).toUpperCase(), v -> (int) v.get("creditBureauConfigurationId"))); - final Object usernameConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("USERNAME").intValue(), "USERNAME", "testUser"); - Assertions.assertNotNull(usernameConfigurationId); - final Object passwordConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("PASSWORD").intValue(), "PASSWORD", "testPassword"); - Assertions.assertNotNull(passwordConfigurationId); - final Object creditReportUrlConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("CREDITREPORTURL").intValue(), "CREDITREPORTURL", - "http://localhost:3558/report/"); - Assertions.assertNotNull(creditReportUrlConfigurationId); - final Object searchUrlConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("SEARCHURL").intValue(), "SEARCHURL", "http://localhost:3558/search/"); - Assertions.assertNotNull(searchUrlConfigurationId); - final Object tokenUrlConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("TOKENURL").intValue(), "TOKENURL", "http://localhost:3558/token/"); - Assertions.assertNotNull(tokenUrlConfigurationId); - final Object subscriptionIdConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("SUBSCRIPTIONID").intValue(), "SUBSCRIPTIONID", "subscriptionID123"); - Assertions.assertNotNull(subscriptionIdConfigurationId); - final Object subscriptionKeyConfigurationId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("SUBSCRIPTIONKEY").intValue(), "SUBSCRIPTIONKEY", "subscriptionKey456"); - Assertions.assertNotNull(subscriptionKeyConfigurationId); - final Object addCreditReportUrlId = CreditBureauConfigurationHelper.updateCreditBureauConfiguration(this.requestSpec, - this.responseSpec, currentConfiguration.get("ADDCREDITREPORTURL").intValue(), "addCreditReporturl", - "http://localhost:3558/upload/"); - Assertions.assertNotNull(addCreditReportUrlId); - + Map currentConfiguration = configurations.stream() + .collect(Collectors.toMap(k -> String.valueOf(k.get("configurationKey")).toUpperCase(), + v -> ((Number) v.get("creditBureauConfigurationId")).longValue())); + final String usernameResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("USERNAME"), "USERNAME", "testUser"); + Assertions.assertNotNull(usernameResponse); + final String passwordResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("PASSWORD"), "PASSWORD", "testPassword"); + Assertions.assertNotNull(passwordResponse); + final String creditReportUrlResponse = CreditBureauConfigurationHelper.updateCreditBureauConfiguration( + currentConfiguration.get("CREDITREPORTURL"), "CREDITREPORTURL", "http://localhost:3558/report/"); + Assertions.assertNotNull(creditReportUrlResponse); + final String searchUrlResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("SEARCHURL"), "SEARCHURL", "http://localhost:3558/search/"); + Assertions.assertNotNull(searchUrlResponse); + final String tokenUrlResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("TOKENURL"), "TOKENURL", "http://localhost:3558/token/"); + Assertions.assertNotNull(tokenUrlResponse); + final String subscriptionIdResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("SUBSCRIPTIONID"), "SUBSCRIPTIONID", "subscriptionID123"); + Assertions.assertNotNull(subscriptionIdResponse); + final String subscriptionKeyResponse = CreditBureauConfigurationHelper + .updateCreditBureauConfiguration(currentConfiguration.get("SUBSCRIPTIONKEY"), "SUBSCRIPTIONKEY", "subscriptionKey456"); + Assertions.assertNotNull(subscriptionKeyResponse); + final String addCreditReportUrlResponse = CreditBureauConfigurationHelper.updateCreditBureauConfiguration( + currentConfiguration.get("ADDCREDITREPORTURL"), "addCreditReporturl", "http://localhost:3558/upload/"); + Assertions.assertNotNull(addCreditReportUrlResponse); } @Test @@ -130,11 +118,11 @@ public void creditBureauIntegrationTest() throws JsonProcessingException { + "\"Gender\":\"male\"," + "\"Address\":\"Test Address\"" + "}," + "\"CreditScore\": {\"Score\": \"500\"}," + "\"ActiveLoans\": [\"Loan1\", \"Loan2\"]," + "\"WriteOffLoans\": [\"Loan3\", \"Loan4\"]" + "}}", 200))); - Object serviceResult = CreditBureauIntegrationHelper.getCreditReport(this.requestSpec, this.responseSpec, "1", "NRC213"); + String serviceResult = CreditBureauIntegrationHelper.getCreditReport("1", "NRC213"); Assertions.assertNotNull(serviceResult); Gson gson = new Gson(); CreditBureauReportData responseData = gson.fromJson( - gson.toJson(JsonParser.parseString(String.valueOf(serviceResult)).getAsJsonObject().get("creditBureauReportData")), + gson.toJson(JsonParser.parseString(serviceResult).getAsJsonObject().get("creditBureauReportData")), CreditBureauReportData.class); Assertions.assertEquals("\"Test Name\"", responseData.getName()); Assertions.assertEquals("{\"Score\":\"500\"}", responseData.getCreditScore()); @@ -168,11 +156,11 @@ public void creditBureauNoLoanTest() throws JsonProcessingException { + "\"Name\":\"Test Name\"," + "\"Gender\":\"male\"," + "\"Address\":\"Test Address\"" + "}," + "\"CreditScore\": {\"Score\": \"500\"}," + "\"ActiveLoans\": []," + "\"WriteOffLoans\": []" + "}}", 200))); - Object serviceResult = CreditBureauIntegrationHelper.getCreditReport(this.requestSpec, this.responseSpec, "1", "NRC213"); + String serviceResult = CreditBureauIntegrationHelper.getCreditReport("1", "NRC213"); Assertions.assertNotNull(serviceResult); Gson gson = new Gson(); CreditBureauReportData responseData = gson.fromJson( - gson.toJson(JsonParser.parseString(String.valueOf(serviceResult)).getAsJsonObject().get("creditBureauReportData")), + gson.toJson(JsonParser.parseString(serviceResult).getAsJsonObject().get("creditBureauReportData")), CreditBureauReportData.class); Assertions.assertEquals("\"Test Name\"", responseData.getName()); Assertions.assertEquals("{\"Score\":\"500\"}", responseData.getCreditScore()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java deleted file mode 100644 index a66b60d2417..00000000000 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CurrenciesTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.integrationtests; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.builder.ResponseSpecBuilder; -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; -import java.util.List; -import java.util.Objects; -import org.apache.fineract.integrationtests.common.CurrenciesHelper; -import org.apache.fineract.integrationtests.common.CurrencyDomain; -import org.apache.fineract.integrationtests.common.Utils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -@SuppressWarnings({ "unused" }) -public class CurrenciesTest { - - private ResponseSpecification responseSpec; - private RequestSpecification requestSpec; - - @BeforeEach - public void setup() { - Utils.initializeRESTAssured(); - this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); - } - - @Test - public void testCurrencyElements() { - - CurrencyDomain currency = CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, "USD"); - CurrencyDomain usd = CurrencyDomain.create("USD", "US Dollar", 2, "$", "currency.USD", "US Dollar ($)").build(); - - assertNotNull(currency); - assertTrue(currency.getDecimalPlaces() >= 0); - assertNotNull(currency.getName()); - assertNotNull(currency.getDisplaySymbol()); - assertNotNull(currency.getDisplayLabel()); - assertNotNull(currency.getNameCode()); - - assertEquals(usd, currency); - } - - @Test - public void testUpdateCurrencySelection() { - var currenciestoUpdate = List.of("KES", "BND", "LBP", "GHC", "USD", "INR"); - - var currenciesOutput = CurrenciesHelper.updateSelectedCurrencies(this.requestSpec, this.responseSpec, currenciestoUpdate); - - assertNotNull(currenciesOutput); - assertEquals(currenciestoUpdate, currenciesOutput, "Verifying returned currencies match after update"); - - var currenciesBeforeUpdate = currenciestoUpdate.stream() - .map(currency -> CurrenciesHelper.getCurrencybyCode(requestSpec, responseSpec, currency)).filter(Objects::nonNull).sorted() - .toList(); - - var currenciesAfterUpdate = CurrenciesHelper.getSelectedCurrencies(requestSpec, responseSpec); - - assertNotNull(currenciesAfterUpdate); - assertEquals(currenciesBeforeUpdate, currenciesAfterUpdate, "Verifying selected currencies match after update"); - } -} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FloatingRateInterestRecalculationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FloatingRateInterestRecalculationTest.java new file mode 100644 index 00000000000..e8475a98db9 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FloatingRateInterestRecalculationTest.java @@ -0,0 +1,227 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.HashMap; +import java.util.List; +import org.apache.fineract.client.models.FloatingRatePeriodRequest; +import org.apache.fineract.client.models.FloatingRateRequest; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostFloatingRatesResponse; +import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; +import org.apache.fineract.client.util.Calls; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.integrationtests.common.BusinessDateHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.AccountHelper; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Integration test for Inconsistent Interest Recalculation between exact repayment and over-payment for Cumulative Loan + * with Floating Rates. + * + * @see org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms#updateAnnualNominalInterestRate + */ +public class FloatingRateInterestRecalculationTest extends BaseLoanIntegrationTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private LoanTransactionHelper loanTransactionHelper; + private ClientHelper clientHelper; + private AccountHelper accountHelper; + private final DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + + private static final BigDecimal INITIAL_INTEREST_RATE = new BigDecimal("12"); + private static final BigDecimal CHANGED_INTEREST_RATE = new BigDecimal("6"); + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(true)); + } + + @AfterEach + public void tearDown() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(false)); + } + + @Test + public void testExactRepaymentRecalculatesEmiOnFloatingRateChange() { + runFloatingRateRecalculationScenario(false); + } + + @Test + public void testOverPaymentRecalculatesEmiOnFloatingRateChange() { + runFloatingRateRecalculationScenario(true); + } + + private void runFloatingRateRecalculationScenario(boolean overPayment) { + LocalDate setupDate = LocalDate.of(2024, 2, 1); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, setupDate); + + Long floatingRateId = createFloatingRate(); + + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + Integer loanProductId = createCumulativeFloatingRateLoanProduct(floatingRateId, assetAccount, incomeAccount, expenseAccount, + overpaymentAccount); + assertNotNull(loanProductId); + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + LocalDate disbursementDate = LocalDate.of(2024, 3, 15); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + final Integer loanId = createAndDisburseLoan(clientId, loanProductId, disbursementDate); + assertNotNull(loanId); + + GetLoansLoanIdResponse initialLoan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(initialLoan.getRepaymentSchedule()); + List initialPeriods = initialLoan.getRepaymentSchedule().getPeriods(); + + BigDecimal initialEmi = null; + for (GetLoansLoanIdRepaymentPeriod period : initialPeriods) { + if (period.getPeriod() != null && period.getPeriod() == 1) { + initialEmi = period.getTotalDueForPeriod(); + break; + } + } + assertNotNull(initialEmi, "Could not find initial EMI for period 1"); + assertTrue(initialEmi.compareTo(BigDecimal.ZERO) > 0, "Initial EMI should be greater than zero"); + + LocalDate postRateChangeDate = LocalDate.of(2024, 4, 10); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, postRateChangeDate); + + String repaymentDate = dateFormatter.format(postRateChangeDate); + float repaymentAmount = overPayment ? initialEmi.floatValue() + 0.01f : initialEmi.floatValue(); + loanTransactionHelper.makeRepayment(repaymentDate, repaymentAmount, loanId); + + GetLoansLoanIdResponse updatedLoan = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(updatedLoan.getRepaymentSchedule()); + List updatedPeriods = updatedLoan.getRepaymentSchedule().getPeriods(); + + boolean foundRecalculatedPeriod = false; + for (GetLoansLoanIdRepaymentPeriod period : updatedPeriods) { + if (period.getPeriod() != null && period.getPeriod() > 1) { + BigDecimal updatedEmi = period.getTotalDueForPeriod(); + assertNotNull(updatedEmi, "EMI for period " + period.getPeriod() + " should not be null"); + assertTrue(updatedEmi.compareTo(initialEmi) < 0, + "Period " + period.getPeriod() + " EMI (" + updatedEmi + ") should be lower than initial EMI (" + initialEmi + + ") after rate drop from " + INITIAL_INTEREST_RATE + "% to " + CHANGED_INTEREST_RATE + "%"); + foundRecalculatedPeriod = true; + } + } + assertTrue(foundRecalculatedPeriod, "Should have found at least one recalculated period after the rate change"); + } + + private Long createFloatingRate() { + FloatingRatePeriodRequest initialPeriod = new FloatingRatePeriodRequest().fromDate("01 March 2024") + .interestRate(INITIAL_INTEREST_RATE).isDifferentialToBaseLendingRate(false).locale("en").dateFormat("dd MMMM yyyy"); + + FloatingRatePeriodRequest changedPeriod = new FloatingRatePeriodRequest().fromDate("01 April 2024") + .interestRate(CHANGED_INTEREST_RATE).isDifferentialToBaseLendingRate(false).locale("en").dateFormat("dd MMMM yyyy"); + + FloatingRateRequest floatingRateRequest = new FloatingRateRequest().name(Utils.uniqueRandomStringGenerator("FLOAT_RATE_", 6)) + .isBaseLendingRate(false).isActive(true).ratePeriods(List.of(initialPeriod, changedPeriod)); + + PostFloatingRatesResponse response = Calls.ok(fineractClient().floatingRates.createFloatingRate(floatingRateRequest)); + assertNotNull(response); + assertNotNull(response.getResourceId()); + return response.getResourceId(); + } + + private Integer createCumulativeFloatingRateLoanProduct(Long floatingRateId, Account... accounts) { + final HashMap loanProductMap = new LoanProductTestBuilder().withPrincipal("10000").withNumberOfRepayments("12") + .withRepaymentTypeAsMonth().withRepaymentAfterEvery("1").withInterestTypeAsDecliningBalance() + .withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withInterestRecalculationDetails(LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE) + .withInterestRecalculationRestFrequencyDetails(LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", null, null) + .withDaysInMonth("30").withDaysInYear("360").withAccountingRulePeriodicAccrual(accounts).build(null, null); + + loanProductMap.remove("interestRatePerPeriod"); + loanProductMap.remove("interestRateFrequencyType"); + + loanProductMap.put("isLinkedToFloatingInterestRates", true); + loanProductMap.put("floatingRatesId", floatingRateId); + loanProductMap.put("interestRateDifferential", "0"); + loanProductMap.put("isFloatingInterestRateCalculationAllowed", true); + loanProductMap.put("minDifferentialLendingRate", "0"); + loanProductMap.put("defaultDifferentialLendingRate", "0"); + loanProductMap.put("maxDifferentialLendingRate", "50"); + + return loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap)); + } + + private Integer createAndDisburseLoan(Integer clientId, Integer loanProductId, LocalDate disbursementDate) { + String disburseDateStr = dateFormatter.format(disbursementDate); + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("10000").withLoanTermFrequency("12") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withInterestTypeAsDecliningBalance() + .withExpectedDisbursementDate(disburseDateStr).withSubmittedOnDate(disburseDateStr).withLoanType("individual") + .build(clientId.toString(), loanProductId.toString(), null); + + com.google.gson.JsonObject jsonObject = com.google.gson.JsonParser.parseString(loanApplicationJSON).getAsJsonObject(); + jsonObject.remove("interestRatePerPeriod"); + jsonObject.addProperty("interestRateDifferential", "0"); + jsonObject.addProperty("isFloatingInterestRate", true); + loanApplicationJSON = jsonObject.toString(); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan(disburseDateStr, "10000", loanId, null); + loanTransactionHelper.disburseLoanWithNetDisbursalAmount(disburseDateStr, loanId, "10000"); + return loanId; + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java index 2c6424bf59e..9c8b2d4df7c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java @@ -957,7 +957,7 @@ public void testOnHoldTransactionsApiForGroupSavingsAccount() { // Add the group savings account as guarantor collateral for the loan // Use GUARANTEE_AMOUNT (500) as guarantee amount (less than the MINIMUM_OPENING_BALANCE of 1000) String guarantorJSON = new GuarantorTestBuilder() - .existingCustomerWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), GUARANTEE_AMOUNT).build(); + .existingGroupWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), GUARANTEE_AMOUNT).build(); LOG.info("Guarantor JSON: {}", guarantorJSON); LOG.info("Loan ID: {}, Group ID: {}, Savings ID: {}", loanID, groupID, savingsId); @@ -1023,6 +1023,270 @@ public void testOnHoldTransactionsApiForGroupSavingsAccount() { "Should find at least one on-hold transaction with savingsClientName populated (group name)"); } + /** + * Test that creating a group guarantor with an invalid group ID fails with appropriate error + */ + @Test + public void testGroupGuarantorWithInvalidGroupId() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + // Create a client who will take out the loan + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientID); + + // Create loan product with hold funds + LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4") + .withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withOnHoldFundDetails("0", "0", "0"); + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null)); + Assertions.assertNotNull(loanProductID); + + // Apply for a loan + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(PRINCIPAL).withLoanTermFrequency("4") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("4").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks() + .withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE) + .withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE) + .build(clientID.toString(), loanProductID.toString(), null); + final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + Assertions.assertNotNull(loanID); + + // Try to create guarantor with invalid group ID (9999999) + final Integer invalidGroupId = 9999999; + String guarantorJSON = new GuarantorTestBuilder() + .existingGroupWithGuaranteeAmount(String.valueOf(invalidGroupId), "1", GUARANTEE_AMOUNT).build(); + + final ResponseSpecification errorResponse = new ResponseSpecBuilder().build(); + final RequestSpecification errorRequest = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + errorRequest.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + + ArrayList error = (ArrayList) this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON, errorRequest, + errorResponse); + // Verify we got an error response (status code may be 403 or 404 depending on environment) + Assertions.assertNotNull(error, "Should return error for invalid group ID"); + + LOG.info("SUCCESS: Invalid group ID correctly rejected"); + } + + /** + * + * Test that duplicate group guarantor detection works and shows proper error message with group name + */ + @Test + public void testDuplicateGroupGuarantor() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + // Create client for loan + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientID); + + // Create group with savings account + final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true); + Assertions.assertNotNull(groupID); + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null, + "false"); + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP); + this.savingsAccountHelper.approveSavings(savingsId); + this.savingsAccountHelper.activateSavings(savingsId); + + // Create loan + LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4") + .withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withOnHoldFundDetails("0", "0", "0"); + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null)); + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(PRINCIPAL).withLoanTermFrequency("4") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("4").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks() + .withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE) + .withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE) + .build(clientID.toString(), loanProductID.toString(), null); + final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + // Add group guarantor first time - should succeed + String guarantorJSON = new GuarantorTestBuilder() + .existingGroupWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), GUARANTEE_AMOUNT).build(); + Integer guarantorId1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON); + Assertions.assertNotNull(guarantorId1, "First guarantor creation should succeed"); + + // Try to add the SAME group guarantor again - should fail with duplicate error + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + final RequestSpecification errorRequest = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + errorRequest.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + + ArrayList error = (ArrayList) this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON, errorRequest, + errorResponse); + Assertions.assertNotNull(error, "Should return error for duplicate group guarantor"); + + // Verify error message contains group information + HashMap errorData = error.get(0); + String userMessage = (String) errorData.get("userMessageGlobalisationCode"); + Assertions.assertTrue(userMessage != null && userMessage.contains("already.exist"), + "Error message should indicate duplicate guarantor"); + + LOG.info("SUCCESS: Duplicate group guarantor correctly rejected"); + } + + /** + * Test complete loan lifecycle (approval, disbursement, repayment) with a group guarantor + */ + @Test + public void testGroupGuarantorLoanLifecycle() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + // Create client for loan + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientID); + + // Create group with savings account + final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true); + Assertions.assertNotNull(groupID); + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null, + "false"); + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP); + this.savingsAccountHelper.approveSavings(savingsId); + this.savingsAccountHelper.activateSavings(savingsId); + + // Deposit funds into the savings account to cover the guarantee + this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE, + CommonConstants.RESPONSE_RESOURCE_ID); + + // Create loan product - using minimal hold fund requirements for testing + // Focus is on verifying that group guarantors work, not on complex hold fund logic + LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal("2000").withNumberOfRepayments("1") + .withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withOnHoldFundDetails("0", "0", "0"); + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null)); + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("2000").withLoanTermFrequency("1") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("1").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks() + .withInterestRatePerPeriod("0").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE) + .withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE) + .build(clientID.toString(), loanProductID.toString(), null); + final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + // Add group guarantor with amount = 1000 (50% of 2000 loan) + String guarantorJSON = new GuarantorTestBuilder() + .existingGroupWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), "1000").build(); + Integer guarantorId = this.guarantorHelper.createGuarantor(loanID, guarantorJSON); + Assertions.assertNotNull(guarantorId); + + // Verify guarantor was created successfully + ArrayList guarantors = this.guarantorHelper.getGuarantorList(loanID); + Assertions.assertEquals(1, guarantors.size(), "Should have 1 group guarantor"); + HashMap guarantor = guarantors.get(0); + HashMap guarantorType = (HashMap) guarantor.get("guarantorType"); + Assertions.assertEquals(4, guarantorType.get("id"), "Guarantor type should be GROUP (4)"); + + // Approve loan with group guarantor + HashMap loanStatusHashMap = this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID); + Assertions.assertNotNull(loanStatusHashMap, "Loan approval should succeed with group guarantor"); + + // Disburse loan + this.loanTransactionHelper.disburseLoan(Long.valueOf(loanID), SavingsAccountHelper.TRANSACTION_DATE, 2000.0); + + // Make full repayment + final String repaymentDate = SavingsAccountHelper.TRANSACTION_DATE; + this.loanTransactionHelper.makeRepayment(repaymentDate, Float.parseFloat("2000"), loanID); + + LOG.info("SUCCESS: Group guarantor lifecycle test completed"); + } + + /** + * Test mixed client and group guarantors on the same loan + */ + @Test + public void testMixedClientAndGroupGuarantors() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + // Create loan borrower client + final Integer borrowerClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(borrowerClientID); + + // Create guarantor client with savings + final Integer guarantorClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(guarantorClientID); + + // Create guarantor group with savings + final Integer guarantorGroupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true); + Assertions.assertNotNull(guarantorGroupID); + + // Create savings accounts + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null, + "false"); + + final Integer clientSavingsId = this.savingsAccountHelper.applyForSavingsApplication(guarantorClientID, savingsProductID, + "INDIVIDUAL"); + this.savingsAccountHelper.approveSavings(clientSavingsId); + this.savingsAccountHelper.activateSavings(clientSavingsId); + + final Integer groupSavingsId = this.savingsAccountHelper.applyForSavingsApplication(guarantorGroupID, savingsProductID, + ACCOUNT_TYPE_GROUP); + this.savingsAccountHelper.approveSavings(groupSavingsId); + this.savingsAccountHelper.activateSavings(groupSavingsId); + + // Create loan + LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4") + .withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withOnHoldFundDetails("0", "0", "0"); + final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null)); + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(PRINCIPAL).withLoanTermFrequency("4") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("4").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks() + .withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE) + .withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE) + .build(borrowerClientID.toString(), loanProductID.toString(), null); + final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + // Add CLIENT guarantor + String clientGuarantorJSON = new GuarantorTestBuilder() + .existingCustomerWithGuaranteeAmount(String.valueOf(guarantorClientID), String.valueOf(clientSavingsId), "250").build(); + Integer clientGuarantorId = this.guarantorHelper.createGuarantor(loanID, clientGuarantorJSON); + Assertions.assertNotNull(clientGuarantorId, "Client guarantor creation should succeed"); + + // Add GROUP guarantor + String groupGuarantorJSON = new GuarantorTestBuilder() + .existingGroupWithGuaranteeAmount(String.valueOf(guarantorGroupID), String.valueOf(groupSavingsId), "250").build(); + Integer groupGuarantorId = this.guarantorHelper.createGuarantor(loanID, groupGuarantorJSON); + Assertions.assertNotNull(groupGuarantorId, "Group guarantor creation should succeed"); + + // Retrieve all guarantors for the loan + ArrayList guarantors = this.guarantorHelper.getGuarantorList(loanID); + Assertions.assertNotNull(guarantors, "Should retrieve guarantor list"); + Assertions.assertEquals(2, guarantors.size(), "Should have 2 guarantors (1 client, 1 group)"); + + // Verify both guarantor types are present + boolean hasClientGuarantor = false; + boolean hasGroupGuarantor = false; + + for (HashMap guarantor : guarantors) { + HashMap guarantorType = (HashMap) guarantor.get("guarantorType"); + Integer typeId = (Integer) guarantorType.get("id"); + + if (typeId == 1) { // CUSTOMER/CLIENT + hasClientGuarantor = true; + } else if (typeId == 4) { // GROUP + hasGroupGuarantor = true; + } + } + + Assertions.assertTrue(hasClientGuarantor, "Should have client guarantor"); + Assertions.assertTrue(hasGroupGuarantor, "Should have group guarantor"); + + // Approve loan - both holds should be placed + this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID); + + LOG.info("SUCCESS: Mixed client and group guarantors work together"); + } + @Test public void testGroupAccountAvailableBalance() { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductBasicDetailsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductBasicDetailsTest.java new file mode 100644 index 00000000000..acb30adddba --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductBasicDetailsTest.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collection; +import org.apache.fineract.client.models.LoanProductBasicDetailsData; +import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; +import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(LoanTestLifecycleExtension.class) +public class LoanProductBasicDetailsTest { + + @Test + public void loanProductBasicDetailsTest() { + final Collection productList = LoanProductHelper.fetchProductBasicDetailsList(); + assertNotNull(productList); + assertTrue(!productList.isEmpty()); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java index 91dd1d097e3..a6a92a64158 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/MakercheckerTest.java @@ -36,16 +36,20 @@ import org.apache.fineract.integrationtests.common.AuditHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.FineractClientHelper; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.commands.MakercheckersHelper; import org.apache.fineract.integrationtests.common.organisation.StaffHelper; import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; +import org.apache.fineract.integrationtests.common.system.DatatableHelper; import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper; import org.apache.fineract.integrationtests.useradministration.users.UserHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; @SuppressWarnings({ "unused" }) public class MakercheckerTest { @@ -195,6 +199,78 @@ public void testMakerCheckerOn() { } } + @ParameterizedTest + @ValueSource(strings = { "m_client", "m_group", "m_center", "m_loan", "m_office", "m_savings_account" }) + public void testRejectDatatableCreationCleansUpOrphanedTable(String apptableName) { + + // enable maker-checker globally + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.MAKER_CHECKER, + new PutGlobalConfigurationsRequest().enabled(true)); + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_SAME_MAKER_CHECKER, + new PutGlobalConfigurationsRequest().enabled(false)); + + try { + // enable maker-checker for datatable creation + PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("CREATE_DATATABLE", true); + rolesHelper.updatePermissions(putPermissionsRequest); + + // create role with permissions for maker and checker + Integer roleId = RolesHelper.createRole(requestSpec, responseSpec); + Map permissionMap = Map.of("CREATE_DATATABLE", true, "CREATE_DATATABLE_CHECKER", true); + RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap); + + // create maker user + Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec); + String maker = Utils.uniqueRandomStringGenerator("user", 8); + Integer makerUserId = (Integer) UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, maker, + "A1b2c3d4e5f$", "resourceId"); + + // create checker user + String checker = Utils.uniqueRandomStringGenerator("user", 8); + UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, checker, "A1b2c3d4e5f$", "resourceId"); + + RequestSpecification makerRequestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build() + .header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey(maker, "A1b2c3d4e5f$")); + + // maker creates datatable with maker-checker enabled, this creates the physical table but queues for + // approval + DatatableHelper makerDatatableHelper = new DatatableHelper(makerRequestSpec, this.responseSpec); + String datatableJson = DatatableHelper.getTestDatatableAsJSON(apptableName, false); + String datatableName = com.google.gson.JsonParser.parseString(datatableJson).getAsJsonObject().get("datatableName") + .getAsString(); + makerDatatableHelper.createDatatable(datatableJson, ""); + + // find the pending command + List> auditDetails = makercheckersHelper + .getMakerCheckerList(Map.of("actionName", "CREATE", "entityName", "DATATABLE", "makerId", makerUserId.toString())); + assertEquals(1, auditDetails.size(), "Error: Expected only one pending CREATE DATATABLE command"); + Long commandId = ((Double) auditDetails.get(0).get("id")).longValue(); + + // checker rejects the command which should drop the orphaned table + MakercheckersHelper.rejectMakerCheckerEntry(FineractClientHelper.createNewFineractClient(checker, "A1b2c3d4e5f$"), commandId); + + // verify the datatable no longer exists by trying to create it again + // verify without maker checker, so transaction rollback in postgres doesn't break the test + putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("CREATE_DATATABLE", false); + rolesHelper.updatePermissions(putPermissionsRequest); + + DatatableHelper adminDatatableHelper = new DatatableHelper(this.requestSpec, this.responseSpec); + String recreatedName = adminDatatableHelper.createDatatable(datatableJson, "resourceIdentifier"); + assertEquals(datatableName, recreatedName, "Error: Was not able to recreate datatable after rejection cleanup"); + + // cleanup after test + adminDatatableHelper.deleteDatatable(datatableName); + } finally { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.MAKER_CHECKER, + new PutGlobalConfigurationsRequest().enabled(false)); + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_SAME_MAKER_CHECKER, + new PutGlobalConfigurationsRequest().enabled(true)); + + PutPermissionsRequest putPermissionsRequest = new PutPermissionsRequest().putPermissionsItem("CREATE_DATATABLE", false); + rolesHelper.updatePermissions(putPermissionsRequest); + } + } + private Integer createSavingsProductDailyPosting() { final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() .withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance().build(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/PasswordResetIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/PasswordResetIntegrationTest.java new file mode 100644 index 00000000000..834860b188e --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/PasswordResetIntegrationTest.java @@ -0,0 +1,126 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.restassured.RestAssured; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.ArrayList; +import java.util.List; +import org.apache.fineract.client.models.PostUsersRequest; +import org.apache.fineract.client.models.PostUsersResponse; +import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.integrationtests.client.IntegrationTest; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.useradministration.users.UserHelper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PasswordResetIntegrationTest extends IntegrationTest { + + private RequestSpecification requestSpec; + private ResponseSpecification responseSpec; + private GlobalConfigurationHelper globalConfigurationHelper; + private List transientUsers = new ArrayList<>(); + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.globalConfigurationHelper = new GlobalConfigurationHelper(); + } + + @AfterEach + public void tearDown() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_PASSWORD_RESET_ON_FIRST_LOGIN, + new PutGlobalConfigurationsRequest().value(0L).enabled(false)); + + for (Integer userId : this.transientUsers) { + UserHelper.deleteUser(this.requestSpec, this.responseSpec, userId); + } + this.transientUsers.clear(); + } + + @Test + public void testPasswordResetEnforcement() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_PASSWORD_RESET_ON_FIRST_LOGIN, + new PutGlobalConfigurationsRequest().value(0L).enabled(true)); + + String password = "Abcdef1#2$3%XYZ"; + PostUsersRequest userRequest = UserHelper.buildUserRequest(responseSpec, requestSpec, password); + PostUsersResponse userResponse = UserHelper.createUser(requestSpec, responseSpec, userRequest); + Long userId = userResponse.getResourceId(); + assertNotNull(userId, "User creation failed to return an ID!"); + this.transientUsers.add(userId.intValue()); + String username = userRequest.getUsername(); + + Response loginResponse = attemptLogin(username, password); + assertEquals(403, loginResponse.getStatusCode(), "User should be forced to change password"); + + String newPassword = "Abcdef1#2$3%XYZ_NEW"; + Response changePasswordResponse = changePassword(username, password, userId, newPassword); + assertEquals(200, changePasswordResponse.getStatusCode(), "Password change should succeed"); + + loginResponse = attemptLogin(username, newPassword); + assertEquals(200, loginResponse.getStatusCode(), "User should be able to login after reset"); + } + + @Test + public void testFeatureDisabledByDefault() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_PASSWORD_RESET_ON_FIRST_LOGIN, + new PutGlobalConfigurationsRequest().value(0L).enabled(false)); + + String password = "Abcdef1#2$3%XYZ"; + PostUsersRequest userRequest = UserHelper.buildUserRequest(responseSpec, requestSpec, password); + PostUsersResponse userResponse = UserHelper.createUser(requestSpec, responseSpec, userRequest); + assertNotNull(userResponse.getResourceId(), "User creation failed!"); + this.transientUsers.add(userResponse.getResourceId().intValue()); + String username = userRequest.getUsername(); + + Response loginResponse = attemptLogin(username, password); + assertEquals(200, loginResponse.getStatusCode(), "User should login normally when feature is disabled"); + } + + private Response attemptLogin(String username, String password) { + return RestAssured.given().contentType(ContentType.JSON) + .body("{\"username\":\"" + username + "\", \"password\":\"" + password + "\"}") + .post("/fineract-provider/api/v1/authentication?" + Utils.TENANT_IDENTIFIER); + } + + private Response changePassword(String username, String password, Long userId, String newPassword) { + String authKey = java.util.Base64.getEncoder().encodeToString((username + ":" + password).getBytes(UTF_8)); + return RestAssured.given().contentType(ContentType.JSON).header("Authorization", "Basic " + authKey) + .header("Fineract-Platform-TenantId", "default") + .body("{\"password\":\"" + newPassword + "\", \"repeatPassword\":\"" + newPassword + "\"}") + .post("/fineract-provider/api/v1/users/" + userId + "/pwd?" + Utils.TENANT_IDENTIFIER); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountForceWithdrawalTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountForceWithdrawalTest.java new file mode 100644 index 00000000000..d5f2f48b889 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountForceWithdrawalTest.java @@ -0,0 +1,113 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import org.apache.fineract.client.models.GlobalConfigurationPropertyData; +import org.apache.fineract.client.models.PostSavingsAccountTransactionsRequest; +import org.apache.fineract.client.models.PostSavingsAccountTransactionsResponse; +import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; +import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SavingsAccountForceWithdrawalTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private SavingsProductHelper savingsProductHelper; + private SavingsAccountHelper savingsAccountHelper; + private GlobalConfigurationHelper globalConfigurationHelper; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.savingsProductHelper = new SavingsProductHelper(); + this.globalConfigurationHelper = new GlobalConfigurationHelper(); + } + + @Test + public void testForceWithdrawal() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT, + new PutGlobalConfigurationsRequest().enabled(true)); + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT, + new PutGlobalConfigurationsRequest().value(5000L).enabled(true)); + + GlobalConfigurationPropertyData config = globalConfigurationHelper + .getGlobalConfigurationByName(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT); + Assertions.assertEquals(5000L, config.getValue()); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + final Integer savingsProductId = createSavingsProductDailyPosting(); + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductId, "INDIVIDUAL"); + this.savingsAccountHelper.approveSavings(savingsId); + this.savingsAccountHelper.activateSavings(savingsId); + + this.savingsAccountHelper.depositToSavingsAccount(savingsId, "100", "04 March 2013", null); + + PostSavingsAccountTransactionsRequest request = new PostSavingsAccountTransactionsRequest() // + .locale("en") // + .dateFormat("dd MMMM yyyy") // + .transactionDate("05 March 2013") // + .transactionAmount(java.math.BigDecimal.valueOf(200.0)) // + .paymentTypeId(1); + + retrofit2.Response response = this.savingsAccountHelper + .forceWithdrawalFromSavingsAccount(savingsId.longValue(), request); + + Assertions.assertTrue(response.isSuccessful(), () -> "Force withdrawal failed with body: " + getErrorBody(response)); + } + + private Integer createSavingsProductDailyPosting() { + final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() + .withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance().build(); + return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); + } + + private String getErrorBody(retrofit2.Response response) { + try { + return response.errorBody() != null ? response.errorBody().string() : "No error body"; + } catch (Exception e) { + return "Failed to read error body: " + e.getMessage(); + } + } + + @AfterEach + public void tearDown() { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT, + new PutGlobalConfigurationsRequest().enabled(false)); + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT, + new PutGlobalConfigurationsRequest().value(0L).enabled(false)); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsExternalIdTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsExternalIdTest.java index 5f9e8277407..e02c95920bf 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsExternalIdTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsExternalIdTest.java @@ -19,8 +19,6 @@ package org.apache.fineract.integrationtests; import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.UUID; import org.apache.fineract.client.models.DeleteSavingsAccountsAccountIdResponse; @@ -33,6 +31,7 @@ import org.apache.fineract.client.models.SavingsAccountData; import org.apache.fineract.client.util.Calls; import org.apache.fineract.integrationtests.client.IntegrationTest; +import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.savings.SavingsTestLifecycleExtension; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -48,7 +47,7 @@ public class SavingsAccountsExternalIdTest extends IntegrationTest { public static final String EXTERNAL_ID = UUID.randomUUID().toString(); private final String dateFormat = "dd MMMM yyyy"; private final String locale = "en"; - private final String formattedDate = LocalDate.now(ZoneId.systemDefault()).minusDays(5).format(DateTimeFormatter.ofPattern(dateFormat)); + private final String formattedDate = Utils.getLocalDateOfTenant().format(DateTimeFormatter.ofPattern(dateFormat)); @Test @Order(1) @@ -59,10 +58,10 @@ void submitSavingsAccountsApplication() { request.setProductId(1L); request.setLocale(locale); request.setDateFormat(dateFormat); - request.submittedOnDate(formattedDate); + request.setSubmittedOnDate(formattedDate); request.setExternalId(EXTERNAL_ID); - Response response = okR(fineractClient().savingsAccounts.submitApplication2(request)); + Response response = okR(fineractClient().savingsAccounts.submitSavingsApplication(request)); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -75,7 +74,8 @@ void updateSavingsAccountWithExternalId() { PutSavingsAccountsAccountIdRequest request = new PutSavingsAccountsAccountIdRequest(); request.setLocale(locale); request.setNominalAnnualInterestRate(5.999); - Response response = okR(fineractClient().savingsAccounts.update22(EXTERNAL_ID, request, "")); + Response response = okR( + fineractClient().savingsAccounts.updateSavingsAccountByExternalId(EXTERNAL_ID, request, "")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -90,7 +90,7 @@ void approveSavingsAccount() { request.setLocale(locale); request.setApprovedOnDate(formattedDate); Response response = okR( - fineractClient().savingsAccounts.handleCommands7(EXTERNAL_ID, request, "approve")); + fineractClient().savingsAccounts.handleCommandsSavingsAccountByExternalId(EXTERNAL_ID, request, "approve")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -104,7 +104,8 @@ void retrieveSavingsAccountWithExternalId() { request.dateFormat(dateFormat); request.setLocale(locale); request.setActivatedOnDate(formattedDate); - Response response = okR(fineractClient().savingsAccounts.retrieveOne27(EXTERNAL_ID, false, null, "all")); + Response response = okR( + fineractClient().savingsAccounts.retrieveSavingsAccountByExternalId(EXTERNAL_ID, false, null, "all")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -118,7 +119,7 @@ void undoApprovalSavingsAccountWithExternalId() { LOG.info("------------------------------ UNDO APPROVAL SAVINGS ACCOUNT ---------------------------------------"); PostSavingsAccountsAccountIdRequest request = new PostSavingsAccountsAccountIdRequest(); Response response = okR( - fineractClient().savingsAccounts.handleCommands7(EXTERNAL_ID, request, "undoapproval")); + fineractClient().savingsAccounts.handleCommandsSavingsAccountByExternalId(EXTERNAL_ID, request, "undoapproval")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -132,7 +133,8 @@ void retrieveSavingsAccountWithExternalIdSecondTime() { request.dateFormat(dateFormat); request.setLocale(locale); request.setActivatedOnDate(formattedDate); - Response response = okR(fineractClient().savingsAccounts.retrieveOne27(EXTERNAL_ID, false, null, "all")); + Response response = okR( + fineractClient().savingsAccounts.retrieveSavingsAccountByExternalId(EXTERNAL_ID, false, null, "all")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -147,7 +149,8 @@ void deleteSavingsAccountWithExternalId() { request.dateFormat(dateFormat); request.setLocale(locale); request.setActivatedOnDate(formattedDate); - Response response = okR(fineractClient().savingsAccounts.delete20(EXTERNAL_ID)); + Response response = okR( + fineractClient().savingsAccounts.deleteSavingsAccountByExternalId(EXTERNAL_ID)); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -162,7 +165,7 @@ void retrieveSavingsAccountWithExternalIdThirdTime() { request.setLocale(locale); request.setActivatedOnDate(formattedDate); Response response = Calls - .executeU(fineractClient().savingsAccounts.retrieveOne27(EXTERNAL_ID, false, null, "all")); + .executeU(fineractClient().savingsAccounts.retrieveSavingsAccountByExternalId(EXTERNAL_ID, false, null, "all")); assertThat(response.raw().code()).isEqualTo(404); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsTest.java index 4beb876bcc2..97b13f652ad 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountsTest.java @@ -60,7 +60,7 @@ void submitSavingsAccountsApplication() { request.setDateFormat(dateFormat); request.submittedOnDate(formattedDate); - Response response = okR(fineractClient().savingsAccounts.submitApplication2(request)); + Response response = okR(fineractClient().savingsAccounts.submitSavingsApplication(request)); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -76,7 +76,7 @@ void approveSavingsAccount() { request.setLocale(locale); request.setApprovedOnDate(formattedDate); Response response = okR( - fineractClient().savingsAccounts.handleCommands6((long) savingId, request, "approve")); + fineractClient().savingsAccounts.handleCommandsSavingsAccount((long) savingId, request, "approve")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); @@ -91,7 +91,7 @@ void activateSavingsAccount() { request.setLocale(locale); request.setActivatedOnDate(formattedDate); Response response = okR( - fineractClient().savingsAccounts.handleCommands6((long) savingId, request, "activate")); + fineractClient().savingsAccounts.handleCommandsSavingsAccount((long) savingId, request, "activate")); assertThat(response.isSuccessful()).isTrue(); assertThat(response.body()).isNotNull(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SearchResourcesTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SearchResourcesTest.java index 59be82bb299..bdb751034c3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SearchResourcesTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SearchResourcesTest.java @@ -29,12 +29,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.apache.fineract.client.models.GetClientsClientIdResponse; import org.apache.fineract.client.models.GetSearchResponse; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.SearchHelper; import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; +import org.apache.fineract.integrationtests.common.shares.ShareAccountHelper; +import org.apache.fineract.integrationtests.common.shares.ShareAccountTransactionHelper; +import org.apache.fineract.integrationtests.common.shares.ShareProductHelper; +import org.apache.fineract.integrationtests.common.shares.ShareProductTransactionHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -102,6 +108,83 @@ public void searchAnyValueOverLoanResources() { assertEquals(0, searchResponse.size()); } + @Test + public void searchOverSavingsResources() { + final List resources = Arrays.asList("savings"); + + String jsonPayload = ClientHelper.getBasicClientAsJSON(ClientHelper.DEFAULT_OFFICE_ID, ClientHelper.LEGALFORM_ID_PERSON, null); + final PostClientsResponse clientResponse = ClientHelper.addClientAsPerson(requestSpec, responseSpec, jsonPayload); + final Long clientId = clientResponse.getClientId(); + + final Integer savingsId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId.intValue(), "1000"); + final SavingsAccountHelper savingsAccountHelper = new SavingsAccountHelper(requestSpec, responseSpec); + final String query = (String) savingsAccountHelper.getSavingsAccountDetail(savingsId, "accountNo"); + + final ArrayList searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.FALSE, + getResources(resources)); + + assertNotNull(searchResponse); + assertEquals(1, searchResponse.size()); + + final GetSearchResponse result = searchResponse.getFirst(); + + assertEquals("SAVING", result.getEntityType()); + assertNotNull(result.getEntityStatus()); + assertNotNull(result.getEntityStatus().getId()); + assertNotNull(result.getEntityStatus().getCode()); + assertNotNull(result.getEntityStatus().getValue()); + } + + @Test + public void searchOverSharesResources() { + final List resources = Arrays.asList("shares"); + + String jsonPayload = ClientHelper.getBasicClientAsJSON(ClientHelper.DEFAULT_OFFICE_ID, ClientHelper.LEGALFORM_ID_PERSON, null); + final PostClientsResponse clientsResponse = ClientHelper.addClientAsPerson(requestSpec, responseSpec, jsonPayload); + final Long clientId = clientsResponse.getClientId(); + + final ShareProductHelper shareProductHelper = new ShareProductHelper(); + final Integer productId = ShareProductTransactionHelper.createShareProduct(shareProductHelper.build(), requestSpec, responseSpec); + + final Integer savingsId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId.intValue(), "1000"); + + final String shareJson = new ShareAccountHelper().withClientId(String.valueOf(clientId)).withProductId(String.valueOf(productId)) + .withSavingsAccountId(String.valueOf(savingsId)).withSubmittedDate("01 January 2026").withApplicationDate("01 January 2026") + .withRequestedShares("10").build(); + + final Integer shareAccountId = ShareAccountTransactionHelper.createShareAccount(shareJson, requestSpec, responseSpec); + + final String approveJson = "{}"; + ShareAccountTransactionHelper.postCommand("approve", shareAccountId, approveJson, requestSpec, responseSpec); + + final String activateJson = """ + { + "activatedDate": "01 January 2026", + "dateFormat": "dd MMMM yyyy", + "locale": "en" + } + """; + ShareAccountTransactionHelper.postCommand("activate", shareAccountId, activateJson, requestSpec, responseSpec); + + final Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + final String query = (String) shareAccountData.get("accountNo"); + + final ArrayList searchResponse = SearchHelper.getSearch(requestSpec, responseSpec, query, Boolean.FALSE, + getResources(resources)); + + assertNotNull(searchResponse); + assertEquals(1, searchResponse.size()); + + final GetSearchResponse result = searchResponse.getFirst(); + + assertEquals("SHARE", result.getEntityType()); + assertNotNull(result.getEntityStatus()); + assertNotNull(result.getEntityStatus().getId()); + assertNotNull(result.getEntityStatus().getCode()); + assertNotNull(result.getEntityStatus().getValue()); + } + private String getResources(final List resources) { return String.join(",", resources); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java new file mode 100644 index 00000000000..04193ff99bd --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.HashMap; +import java.util.List; +import org.apache.fineract.integrationtests.common.CommonConstants; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.organisation.CampaignsHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.junit.jupiter.MockServerExtension; +import org.mockserver.junit.jupiter.MockServerSettings; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; +import org.mockserver.model.MediaType; + +/** + * Integration tests for SMS Campaign duplicate name validation. + */ +@ExtendWith(MockServerExtension.class) +@MockServerSettings(ports = { 9191 }) +public class SmsCampaignIntegrationTest { + + private RequestSpecification requestSpec; + private ResponseSpecification responseSpec; + private ResponseSpecification errorResponseSpec; + private CampaignsHelper campaignsHelper; + private final ClientAndServer client; + + public SmsCampaignIntegrationTest(ClientAndServer client) { + this.client = client; + this.client.when(HttpRequest.request().withMethod("GET").withPath("/smsbridges")) + .respond(HttpResponse.response().withContentType(MediaType.APPLICATION_JSON).withBody( + "[{\"id\":1,\"tenantId\":1,\"phoneNo\":\"+1234567890\",\"providerName\":\"Dummy SMS Provider - Testing\",\"providerDescription\":\"Dummy, just for testing\"}]")); + } + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.requestSpec.header("Fineract-Platform-TenantId", "default"); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.errorResponseSpec = new ResponseSpecBuilder().expectStatusCode(403).build(); + this.campaignsHelper = new CampaignsHelper(this.requestSpec, this.responseSpec); + } + + @Test + public void testCreateCampaignWithDuplicateNameShouldFail() { + String reportName = "Prospective Clients"; + int triggerType = 1; + String campaignName = "Duplicate_Test_Campaign_" + System.currentTimeMillis(); + + // Create first campaign with specific name + Integer firstCampaignId = campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName); + assertNotNull(firstCampaignId, "First campaign should be created successfully"); + campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, responseSpec, firstCampaignId); + + // Attempt to create second campaign with the same name - should fail + List errors = campaignsHelper.createCampaignWithNameExpectingError(errorResponseSpec, reportName, triggerType, + campaignName); + + assertNotNull(errors, "Error response should not be null"); + assertEquals(1, errors.size(), "Should have exactly one error"); + assertEquals("error.msg.sms.campaign.duplicate.name", errors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE), + "Error code should indicate duplicate campaign name"); + } + + @Test + public void testCreateCampaignWithUniqueNameShouldSucceed() { + String reportName = "Prospective Clients"; + int triggerType = 1; + String campaignName1 = "Unique_Campaign_1_" + System.currentTimeMillis(); + String campaignName2 = "Unique_Campaign_2_" + System.currentTimeMillis(); + + // Create first campaign + Integer firstCampaignId = campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName1); + assertNotNull(firstCampaignId, "First campaign should be created successfully"); + + // Create second campaign with different name - should succeed + Integer secondCampaignId = campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName2); + assertNotNull(secondCampaignId, "Second campaign with different name should be created successfully"); + + // Verify both campaigns exist + campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, responseSpec, firstCampaignId); + campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, responseSpec, secondCampaignId); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserAdministrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserAdministrationTest.java index e2416f7aa71..b4bb2123c21 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserAdministrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UserAdministrationTest.java @@ -202,19 +202,19 @@ public void testApplicationUserCanUpdateOwnPassword() { // User updates its own password String updatedPassword = "QwE!5rTy#9uP0u"; - PutUsersUserIdResponse putUsersUserIdResponse = ok(newFineractClient(simpleUsername, originalPassword).users.update27(userId, + PutUsersUserIdResponse putUsersUserIdResponse = ok(newFineractClient(simpleUsername, originalPassword).users.updateUser(userId, new PutUsersUserIdRequest().password(updatedPassword).repeatPassword(updatedPassword))); Assertions.assertNotNull(putUsersUserIdResponse.getResourceId()); // From then on the originalPassword is not working anymore CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, () -> { - ok(newFineractClient(simpleUsername, originalPassword).users.retrieveOne32(userId)); + ok(newFineractClient(simpleUsername, originalPassword).users.retrieveOneUser(userId)); }); Assertions.assertEquals(401, callFailedRuntimeException.getResponse().raw().code()); Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("Unauthorized")); // The update password is still working perfectly - GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOne32(userId)); + GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOneUser(userId)); } @Test @@ -237,18 +237,18 @@ public void testApplicationUserCanChangeOwnPassword() { String updatedPassword = "pX268-4Pfv|kF6"; ChangePwdUsersUserIdResponse changePwdUsersUserIdResponse = ok(newFineractClient(simpleUsername, originalPassword).users - .changePassword(userId, new ChangePwdUsersUserIdRequest().password(updatedPassword).repeatPassword(updatedPassword))); + .changePasswordUser(userId, new ChangePwdUsersUserIdRequest().password(updatedPassword).repeatPassword(updatedPassword))); Assertions.assertNotNull(changePwdUsersUserIdResponse.getResourceId()); // From then on the originalPassword is not working anymore CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, () -> { - ok(newFineractClient(simpleUsername, originalPassword).users.retrieveOne32(userId)); + ok(newFineractClient(simpleUsername, originalPassword).users.retrieveOneUser(userId)); }); Assertions.assertEquals(401, callFailedRuntimeException.getResponse().raw().code()); Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("Unauthorized")); // The update password is still working perfectly - GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOne32(userId)); + GetUsersUserIdResponse ok = ok(newFineractClient(simpleUsername, updatedPassword).users.retrieveOneUser(userId)); } @Test @@ -271,7 +271,7 @@ public void testApplicationUserShallNotBeAbleToChangeItsOwnRoles() { // User tries to update it's own roles CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class, () -> { - ok(newFineractClient(simpleUsername, password).users.update27(userId, + ok(newFineractClient(simpleUsername, password).users.updateUser(userId, new PutUsersUserIdRequest().roles(List.of(Long.valueOf(roleId2))))); }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductCRUDTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductCRUDTest.java new file mode 100644 index 00000000000..5a7749d5755 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductCRUDTest.java @@ -0,0 +1,486 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import org.apache.fineract.client.models.DeleteWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetDelinquencyBucket; +import org.apache.fineract.client.models.GetDelinquencyRange; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsTemplateResponse; +import org.apache.fineract.client.models.PaymentAllocationOrder; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.StringEnumOptionData; +import org.apache.fineract.integrationtests.common.workingcapitalloanproduct.WorkingCapitalLoanProductHelper; +import org.apache.fineract.integrationtests.common.workingcapitalloanproduct.WorkingCapitalLoanProductTestBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WorkingCapitalLoanProductCRUDTest { + + private WorkingCapitalLoanProductHelper wclProductHelper; + + @BeforeEach + public void setup() { + this.wclProductHelper = new WorkingCapitalLoanProductHelper(); + } + + @Test + public void testCreateWorkingCapitalLoanProduct() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).build(); + + // When + final PostWorkingCapitalLoanProductsResponse response = wclProductHelper.createWorkingCapitalLoanProduct(request); + + // Then + assertNotNull(response); + assertNotNull(response.getResourceId()); + final Long productId = response.getResourceId(); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testRetrieveWorkingCapitalLoanProductById() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).build(); + final PostWorkingCapitalLoanProductsResponse createResponse = wclProductHelper.createWorkingCapitalLoanProduct(request); + final Long productId = createResponse.getResourceId(); + + // When + final GetWorkingCapitalLoanProductsProductIdResponse response = wclProductHelper.retrieveWorkingCapitalLoanProductById(productId); + + // Then + assertNotNull(response); + assertEquals(productId, response.getId()); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testRetrieveWorkingCapitalLoanProductByExternalId() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final String externalId = UUID.randomUUID().toString(); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).withExternalId(externalId).build(); + final PostWorkingCapitalLoanProductsResponse createResponse = wclProductHelper.createWorkingCapitalLoanProduct(request); + final Long productId = createResponse.getResourceId(); + + // When + final GetWorkingCapitalLoanProductsProductIdResponse response = wclProductHelper + .retrieveWorkingCapitalLoanProductByExternalId(externalId); + + // Then + assertNotNull(response); + assertEquals(productId, response.getId()); + assertEquals(externalId, response.getExternalId()); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testRetrieveAllWorkingCapitalLoanProducts() { + // Given + final String uniqueId1 = UUID.randomUUID().toString().substring(0, 8); + final String uniqueId2 = UUID.randomUUID().toString().substring(0, 8); + final PostWorkingCapitalLoanProductsRequest request1 = new WorkingCapitalLoanProductTestBuilder() + .withName("wcl Product 1 " + uniqueId1).withShortName("W1" + uniqueId1.substring(0, 2)).build(); + final PostWorkingCapitalLoanProductsRequest request2 = new WorkingCapitalLoanProductTestBuilder() + .withName("wcl Product 2 " + uniqueId2).withShortName("W2" + uniqueId2.substring(0, 2)).build(); + final Long productId1 = wclProductHelper.createWorkingCapitalLoanProduct(request1).getResourceId(); + final Long productId2 = wclProductHelper.createWorkingCapitalLoanProduct(request2).getResourceId(); + + // When + final List response = wclProductHelper.retrieveAllWorkingCapitalLoanProducts(); + + // Then + assertNotNull(response); + assertTrue(response.size() >= 2); + assertTrue(response.stream().anyMatch(p -> { + assertNotNull(p.getId()); + return p.getId().equals(productId1); + })); + assertTrue(response.stream().anyMatch(p -> { + assertNotNull(p.getId()); + return p.getId().equals(productId2); + })); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId1); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId2); + } + + @Test + public void testRetrieveTemplate() { + // When + final GetWorkingCapitalLoanProductsTemplateResponse response = wclProductHelper.retrieveTemplate(); + + // Then + assertNotNull(response); + // Verify options are present + assertNotNull(response.getCurrencyOptions()); + assertFalse(response.getCurrencyOptions().isEmpty(), "Currency options should not be empty"); + assertNotNull(response.getAmortizationTypeOptions()); + assertFalse(response.getAmortizationTypeOptions().isEmpty(), "Amortization type options should not be empty"); + assertNotNull(response.getPeriodFrequencyTypeOptions()); + assertFalse(response.getPeriodFrequencyTypeOptions().isEmpty(), "Period frequency type options should not be empty"); + assertNotNull(response.getAdvancedPaymentAllocationTypes()); + assertFalse(response.getAdvancedPaymentAllocationTypes().isEmpty(), "Payment allocation type options should not be empty"); + assertNotNull(response.getAdvancedPaymentAllocationTransactionTypes()); + assertFalse(response.getAdvancedPaymentAllocationTransactionTypes().isEmpty(), + "Payment allocation transaction type options should not be empty"); + // Verify payment allocation types contain expected values + final List expectedPaymentAllocationTypes = List.of("PENALTY", "FEE", "PRINCIPAL"); + final List actualPaymentAllocationTypes = response.getAdvancedPaymentAllocationTypes().stream() + .map(StringEnumOptionData::getCode).toList(); + assertTrue(actualPaymentAllocationTypes.containsAll(expectedPaymentAllocationTypes), + "Payment allocation types should contain all expected types"); + assertEquals(3, actualPaymentAllocationTypes.size(), "Payment allocation types should have exactly 3 types"); + } + + @Test + public void testUpdateWorkingCapitalLoanProduct() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest createRequest = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(createRequest).getResourceId(); + + // When + final String updatedName = "Updated wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final PutWorkingCapitalLoanProductsProductIdRequest updateRequest = new WorkingCapitalLoanProductTestBuilder().withName(updatedName) + .withShortName(uniqueShortName).buildUpdateRequest(); + final PutWorkingCapitalLoanProductsProductIdResponse updateResponse = wclProductHelper + .updateWorkingCapitalLoanProductById(productId, updateRequest); + + // Then + assertNotNull(updateResponse); + assertEquals(productId, updateResponse.getResourceId()); + final GetWorkingCapitalLoanProductsProductIdResponse retrieved = wclProductHelper.retrieveWorkingCapitalLoanProductById(productId); + assertEquals(updatedName, retrieved.getName()); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testUpdateWorkingCapitalLoanProductByExternalId() { + // Given + final String externalId = UUID.randomUUID().toString(); + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest createRequest = new WorkingCapitalLoanProductTestBuilder().withExternalId(externalId) + .withName(uniqueName).withShortName(uniqueShortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(createRequest).getResourceId(); + + // When + final String updatedName = "Updated wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final PutWorkingCapitalLoanProductsProductIdRequest updateRequest = new WorkingCapitalLoanProductTestBuilder().withName(updatedName) + .withShortName(uniqueShortName).buildUpdateRequest(); + final PutWorkingCapitalLoanProductsProductIdResponse updateResponse = wclProductHelper + .updateWorkingCapitalLoanProductByExternalId(externalId, updateRequest); + + // Then + assertNotNull(updateResponse); + assertEquals(productId, updateResponse.getResourceId()); + final GetWorkingCapitalLoanProductsProductIdResponse retrieved = wclProductHelper.retrieveWorkingCapitalLoanProductById(productId); + assertEquals(updatedName, retrieved.getName()); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testDeleteWorkingCapitalLoanProduct() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request).getResourceId(); + + // When + final DeleteWorkingCapitalLoanProductsProductIdResponse response = wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + + // Then + assertNotNull(response); + assertEquals(productId, response.getResourceId()); + } + + @Test + public void testDeleteWorkingCapitalLoanProductByExternalId() { + // Given + final String externalId = UUID.randomUUID().toString(); + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withExternalId(externalId) + .withName(uniqueName).withShortName(uniqueShortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request).getResourceId(); + + // When + final DeleteWorkingCapitalLoanProductsProductIdResponse response = wclProductHelper + .deleteWorkingCapitalLoanProductByExternalId(externalId); + + // Then + assertNotNull(response); + assertEquals(productId, response.getResourceId()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithAllFields() { + // Given + final String uniqueId = UUID.randomUUID().toString().substring(0, 8); + final String externalId = UUID.randomUUID().toString(); + final List paymentAllocationTypes = List.of("PENALTY", "FEE", "PRINCIPAL"); + final HashMap allowAttributeOverrides = new HashMap<>(); + allowAttributeOverrides.put("amortizationType", true); + allowAttributeOverrides.put("interestType", false); + + // Get fund and delinquency bucket from template + final GetWorkingCapitalLoanProductsTemplateResponse template = wclProductHelper.retrieveTemplate(); + Long fundId = null; + if (template.getFundOptions() != null && !template.getFundOptions().isEmpty()) { + fundId = template.getFundOptions().getFirst().getId(); + } + Long delinquencyBucketId = null; + if (template.getDelinquencyBucketOptions() != null && !template.getDelinquencyBucketOptions().isEmpty()) { + final GetDelinquencyBucket firstBucket = template.getDelinquencyBucketOptions().getFirst(); + if (firstBucket != null && firstBucket.getId() != null) { + delinquencyBucketId = firstBucket.getId(); + } + } + + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withName("Full wcl Product " + uniqueId) // + .withShortName("FW" + uniqueId.substring(0, 2)) // + .withDescription("Full description") // + .withExternalId(externalId) // + .withFundId(fundId) // + .withAmortizationType("EIR") // + .withFlatPercentageAmount(BigDecimal.valueOf(5.0)) // + .withDelinquencyBucketId(delinquencyBucketId) // + .withNpvDayCount(365) // + .withPaymentAllocationTypes(paymentAllocationTypes) // + .withPrincipalAmountMin(BigDecimal.valueOf(1000)) // + .withPrincipalAmountDefault(BigDecimal.valueOf(5000)) // + .withPrincipalAmountMax(BigDecimal.valueOf(10000)) // + .withMinPeriodPaymentRate(BigDecimal.valueOf(0.5)) // + .withPeriodPaymentRate(BigDecimal.valueOf(1.0)) // + .withMaxPeriodPaymentRate(BigDecimal.valueOf(2.0)) // + .withDiscount(BigDecimal.valueOf(0.1)) // + .withRepaymentEvery(60) // + .withRepaymentFrequencyType("DAYS") // + .withAllowAttributeOverrides(allowAttributeOverrides) // + .build(); + + // When + final PostWorkingCapitalLoanProductsResponse response = wclProductHelper.createWorkingCapitalLoanProduct(request); + + // Then + assertNotNull(response); + assertNotNull(response.getResourceId()); + final Long productId = response.getResourceId(); + final GetWorkingCapitalLoanProductsProductIdResponse retrieved = wclProductHelper.retrieveWorkingCapitalLoanProductById(productId); + assertNotNull(retrieved.getName()); + assertTrue(retrieved.getName().startsWith("Full wcl Product")); + assertEquals(externalId, retrieved.getExternalId()); + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testHappyPath_CreateAndRetrieve_VerifyAllFields() { + // Given - Create product with ALL possible fields + final String productName = "Happy Path wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String shortName = "HP" + UUID.randomUUID().toString().substring(0, 2); + final String externalId = UUID.randomUUID().toString(); + final String description = "Comprehensive test product with all fields"; + final List paymentAllocationTypes = List.of("PENALTY", "FEE", "PRINCIPAL"); + + // Get fund and delinquency bucket from template + final GetWorkingCapitalLoanProductsTemplateResponse template = wclProductHelper.retrieveTemplate(); + Long fundId = null; + String fundName = null; + if (template.getFundOptions() != null && !template.getFundOptions().isEmpty()) { + fundId = template.getFundOptions().getFirst().getId(); + fundName = template.getFundOptions().getFirst().getName(); + } + Long delinquencyBucketId = null; + final GetDelinquencyBucket expectedBucket = template.getDelinquencyBucketOptions() != null + && !template.getDelinquencyBucketOptions().isEmpty() ? template.getDelinquencyBucketOptions().getFirst() : null; + if (expectedBucket != null) { + delinquencyBucketId = expectedBucket.getId(); + } + + // All configurable attributes + final HashMap allowAttributeOverrides = new HashMap<>(); + allowAttributeOverrides.put("flatPercentageAmount", true); + allowAttributeOverrides.put("delinquencyBucketClassification", false); + allowAttributeOverrides.put("discountDefault", true); + allowAttributeOverrides.put("periodPaymentFrequency", false); + allowAttributeOverrides.put("periodPaymentFrequencyType", true); + + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + // Details category + .withName(productName) // + .withShortName(shortName) // + .withDescription(description) // + .withExternalId(externalId) // + .withFundId(fundId) // + // Currency category + .withCurrencyCode("USD") // + .withDecimalPlace(2) // + .withCurrencyInMultiplesOf(1) // + // Settings category + .withAmortizationType("FLAT") // + .withFlatPercentageAmount(BigDecimal.valueOf(5.5)) // + .withDelinquencyBucketId(delinquencyBucketId) // + .withNpvDayCount(365) // + .withPaymentAllocationTypes(paymentAllocationTypes) // + // Term category + .withPrincipalAmountMin(BigDecimal.valueOf(1000)) // + .withPrincipalAmountDefault(BigDecimal.valueOf(5000)) // + .withPrincipalAmountMax(BigDecimal.valueOf(10000)) // + .withMinPeriodPaymentRate(BigDecimal.valueOf(0.5)) // + .withPeriodPaymentRate(BigDecimal.valueOf(1.0)) // + .withMaxPeriodPaymentRate(BigDecimal.valueOf(2.0)) // + .withDiscount(BigDecimal.valueOf(0.1)) // + .withRepaymentEvery(30) // + .withRepaymentFrequencyType("DAYS") // + // Configurable attributes + .withAllowAttributeOverrides(allowAttributeOverrides) // + .build(); + + // When - Create product + final PostWorkingCapitalLoanProductsResponse createResponse = wclProductHelper.createWorkingCapitalLoanProduct(request); + assertNotNull(createResponse); + assertNotNull(createResponse.getResourceId()); + final Long productId = createResponse.getResourceId(); + assertTrue(productId > 0); + + // Then - Retrieve and verify ALL fields + final GetWorkingCapitalLoanProductsProductIdResponse retrieved = wclProductHelper.retrieveWorkingCapitalLoanProductById(productId); + assertNotNull(retrieved); + + // Verify Details category + assertEquals(productId, retrieved.getId()); + assertEquals(productName, retrieved.getName()); + assertEquals(shortName, retrieved.getShortName()); + assertEquals(externalId, retrieved.getExternalId()); + assertEquals(description, retrieved.getDescription()); + if (fundId != null) { + assertEquals(fundId, retrieved.getFundId()); + assertEquals(fundName, retrieved.getFundName()); + } + if (expectedBucket != null) { + assertNotNull(retrieved.getDelinquencyBucket(), "delinquencyBucket"); + assertEquals(expectedBucket.getId(), retrieved.getDelinquencyBucket().getId(), "delinquencyBucket.id"); + assertEquals(expectedBucket.getName(), retrieved.getDelinquencyBucket().getName(), "delinquencyBucket.name"); + if (expectedBucket.getRanges() != null && !expectedBucket.getRanges().isEmpty()) { + assertNotNull(retrieved.getDelinquencyBucket().getRanges(), "delinquencyBucket.ranges"); + assertEquals(expectedBucket.getRanges().size(), retrieved.getDelinquencyBucket().getRanges().size(), + "delinquencyBucket.ranges.size"); + for (int i = 0; i < expectedBucket.getRanges().size(); i++) { + final GetDelinquencyRange expectedRange = expectedBucket.getRanges().get(i); + final GetDelinquencyRange actualRange = retrieved.getDelinquencyBucket().getRanges().get(i); + assertEquals(expectedRange.getId(), actualRange.getId()); + assertEquals(expectedRange.getClassification(), actualRange.getClassification()); + assertEquals(expectedRange.getMinimumAgeDays(), actualRange.getMinimumAgeDays()); + assertEquals(expectedRange.getMaximumAgeDays(), actualRange.getMaximumAgeDays()); + } + } + } + + // Verify Currency category + assertNotNull(retrieved.getCurrency()); + assertEquals("USD", retrieved.getCurrency().getCode()); + assertEquals(2, retrieved.getCurrency().getDecimalPlaces()); + assertEquals(1, retrieved.getCurrency().getInMultiplesOf()); + + // Verify Settings category + assertNotNull(retrieved.getAmortizationType()); + assertEquals("FLAT", retrieved.getAmortizationType().getCode()); + if (retrieved.getFlatPercentageAmount() != null) { + assertEquals(0, BigDecimal.valueOf(5.5).compareTo(retrieved.getFlatPercentageAmount())); + } + assertEquals(365, retrieved.getNpvDayCount()); + + // Verify Payment Allocation (if present) + if (retrieved.getPaymentAllocation() != null && !retrieved.getPaymentAllocation().isEmpty()) { + assertFalse(retrieved.getPaymentAllocation().isEmpty()); + final var paymentAllocation = retrieved.getPaymentAllocation().getFirst(); + assertNotNull(paymentAllocation.getTransactionType()); + assertNotNull(paymentAllocation.getPaymentAllocationOrder()); + assertFalse(paymentAllocation.getPaymentAllocationOrder().isEmpty()); + // Verify that paymentAllocationOrder contains the expected allocation types + final List expectedTypes = List.of("PENALTY", "FEE", "PRINCIPAL"); + final List actualTypes = paymentAllocation.getPaymentAllocationOrder().stream() + .map(PaymentAllocationOrder::getPaymentAllocationRule).filter(Objects::nonNull).toList(); + assertTrue(actualTypes.containsAll(expectedTypes) || actualTypes.containsAll(paymentAllocationTypes)); + } + + // Verify Term category + if (retrieved.getMinPrincipal() != null) { + assertEquals(0, BigDecimal.valueOf(1000).compareTo(retrieved.getMinPrincipal())); + } + assertEquals(0, BigDecimal.valueOf(5000).compareTo(retrieved.getPrincipal())); + if (retrieved.getMaxPrincipal() != null) { + assertEquals(0, BigDecimal.valueOf(10000).compareTo(retrieved.getMaxPrincipal())); + } + if (retrieved.getMinPeriodPaymentRate() != null) { + assertEquals(0, BigDecimal.valueOf(0.5).compareTo(retrieved.getMinPeriodPaymentRate())); + } + assertEquals(0, BigDecimal.valueOf(1.0).compareTo(retrieved.getPeriodPaymentRate())); + if (retrieved.getMaxPeriodPaymentRate() != null) { + assertEquals(0, BigDecimal.valueOf(2.0).compareTo(retrieved.getMaxPeriodPaymentRate())); + } + if (retrieved.getDiscount() != null) { + assertEquals(0, BigDecimal.valueOf(0.1).compareTo(retrieved.getDiscount())); + } + assertEquals(30, retrieved.getRepaymentEvery()); + assertNotNull(retrieved.getRepaymentFrequencyType()); + assertEquals("DAYS", retrieved.getRepaymentFrequencyType().getCode()); + + // Verify Configurable Attributes (allowAttributeOverrides) + if (retrieved.getAllowAttributeOverrides() != null) { + // Configurable attributes + assertEquals(Boolean.TRUE, retrieved.getAllowAttributeOverrides().getFlatPercentageAmount()); + assertEquals(Boolean.FALSE, retrieved.getAllowAttributeOverrides().getDelinquencyBucketClassification()); + assertEquals(Boolean.TRUE, retrieved.getAllowAttributeOverrides().getDiscountDefault()); + assertEquals(Boolean.FALSE, retrieved.getAllowAttributeOverrides().getPeriodPaymentFrequency()); + assertEquals(Boolean.TRUE, retrieved.getAllowAttributeOverrides().getPeriodPaymentFrequencyType()); + } + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductValidationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductValidationTest.java new file mode 100644 index 00000000000..a46b7e773cd --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/WorkingCapitalLoanProductValidationTest.java @@ -0,0 +1,785 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.UUID; +import org.apache.fineract.client.feign.ObjectMapperFactory; +import org.apache.fineract.client.feign.util.CallFailedRuntimeException; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.workingcapitalloanproduct.WorkingCapitalLoanProductHelper; +import org.apache.fineract.integrationtests.common.workingcapitalloanproduct.WorkingCapitalLoanProductTestBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WorkingCapitalLoanProductValidationTest { + + private WorkingCapitalLoanProductHelper wclProductHelper; + + @BeforeEach + public void setup() { + this.wclProductHelper = new WorkingCapitalLoanProductHelper(); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingName() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(null).build(); + request.setName(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [name] The parameter `name` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingShortName() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withShortName(null).build(); + request.setShortName(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertTrue(exception.getMessage().contains("validation") || exception.getMessage().contains("required") + || exception.getDeveloperMessage() != null); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingCurrencyCode() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withCurrencyCode(null).build(); + request.setCurrencyCode(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [currencyCode] The parameter `currencyCode` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingDecimalPlace() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withDecimalPlace(null).build(); + request.setDigitsAfterDecimal(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [digitsAfterDecimal] The parameter `digitsAfterDecimal` is mandatory.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidDecimalPlace() { + // Given - decimalPlace must be in range 0-6 (as per LoanProduct logic) + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withDecimalPlace(-1).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [digitsAfterDecimal] The parameter `digitsAfterDecimal` must be between 0 and 6.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDecimalPlaceOutOfRange() { + // Given - decimalPlace must be in range 0-6 + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withDecimalPlace(7).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [digitsAfterDecimal] The parameter `digitsAfterDecimal` must be between 0 and 6.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingCurrencyInMultiplesOf() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withCurrencyInMultiplesOf(null) + .build(); + request.setInMultiplesOf(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [inMultiplesOf] The parameter `inMultiplesOf` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidCurrencyInMultiplesOf() { + // Given - currencyInMultiplesOf must be >= 0 (as per LoanProduct logic) + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withCurrencyInMultiplesOf(-1) + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [inMultiplesOf] The parameter `inMultiplesOf` must be zero or greater.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidAmortization() { + // Given - Missing amortizationType (null) - API should return 400 + final PostWorkingCapitalLoanProductsRequest baseForNull = new WorkingCapitalLoanProductTestBuilder().withAmortizationType("EIR") + .build(); + final PostWorkingCapitalLoanProductsRequest[] requestNullHolder = new PostWorkingCapitalLoanProductsRequest[1]; + try { + final Field field = PostWorkingCapitalLoanProductsRequest.class.getDeclaredField("amortizationType"); + field.setAccessible(true); + field.set(baseForNull, null); + requestNullHolder[0] = baseForNull; + } catch (final Exception e) { + final PostWorkingCapitalLoanProductsRequest tempRequest = new PostWorkingCapitalLoanProductsRequest(); + tempRequest.setName("Test Product"); + tempRequest.setShortName("TP"); + tempRequest.setCurrencyCode("USD"); + tempRequest.setDigitsAfterDecimal(2); + tempRequest.setInMultiplesOf(1); + tempRequest.setNpvDayCount(360); + tempRequest.setPrincipal(BigDecimal.valueOf(10000)); + tempRequest.setPeriodPaymentRate(BigDecimal.valueOf(1.0)); + tempRequest.setRepaymentEvery(30); + tempRequest.setRepaymentFrequencyType(PostWorkingCapitalLoanProductsRequest.RepaymentFrequencyTypeEnum.DAYS); + tempRequest.setLocale("en"); + tempRequest.setDateFormat("yyyy-MM-dd"); + requestNullHolder[0] = tempRequest; + } + + final CallFailedRuntimeException exceptionNull = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(requestNullHolder[0])); + assertEquals(400, exceptionNull.getStatus()); + assertNotNull(exceptionNull.getDeveloperMessage()); + assertEquals("Validation errors: [amortizationType] The parameter `amortizationType` is mandatory.", + exceptionNull.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithFlatAmortizationAndMissingFlatPercentageAmount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withAmortizationType("FLAT") + .build(); + request.setFlatPercentageAmount(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [flatPercentageAmount] The parameter `flatPercentageAmount` is mandatory.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingNpvDayCount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withNpvDayCount(null).build(); + request.setNpvDayCount(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [npvDayCount] The parameter `npvDayCount` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidNpvDayCount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withNpvDayCount(0).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [npvDayCount] The parameter `npvDayCount` must be greater than 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingPrincipalAmountDefault() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withPrincipalAmountDefault(null) + .build(); + request.setPrincipal(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] The parameter `principal` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithNegativePrincipalAmountDefault() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withPrincipalAmountDefault(BigDecimal.valueOf(-100)).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] The parameter `principal` must be greater than 0.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithZeroPrincipalAmountDefault() { + // Given - principal must be > 0 (positiveAmount, not zeroOrPositiveAmount) + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withPrincipalAmountDefault(BigDecimal.ZERO).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] The parameter `principal` must be greater than 0.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidFundId() { + // Given - fundId must be > 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withFundId(-1L).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [fundId] The parameter `fundId` must be greater than 0.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidDelinquencyBucket() { + // Given - delinquencyBucket must be > 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withDelinquencyBucketId(-1L) + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [delinquencyBucketId] The parameter `delinquencyBucketId` must be greater than 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithNegativeDiscountDefault() { + // Given - discount must be >= 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withDiscount(BigDecimal.valueOf(-1)).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [discount] The parameter `discount` must be greater than or equal to 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithZeroDiscountDefault() { + // Given - discount can be 0 (zeroOrPositiveAmount) + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).withDiscount(BigDecimal.ZERO).build(); + + // When & Then - Should succeed (0 is valid) + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request).getResourceId(); + assertTrue(productId != null && productId > 0); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidPrincipalAmountMin() { + // Given - minPrincipal must be > 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withPrincipalAmountMin(BigDecimal.ZERO).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [minPrincipal] The parameter `minPrincipal` must be greater than 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidPrincipalAmountMax() { + // Given - maxPrincipal must be > 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withPrincipalAmountMax(BigDecimal.ZERO).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals( + "Validation errors: [maxPrincipal] The parameter `maxPrincipal` must be greater than 0.; [principal] Failed data validation due to: must.be.less.than.or.equal.to.max.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithZeroPeriodPaymentRateMin() { + // Given - minPeriodPaymentRate can be 0 (zeroOrPositiveAmount) + // Note: periodPaymentRate must be >= min, so we set it to 0 as well + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).withMinPeriodPaymentRate(BigDecimal.ZERO).withPeriodPaymentRate(BigDecimal.ZERO).build(); + + // When & Then - Should succeed (0 is valid for rate) + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request).getResourceId(); + assertTrue(productId != null && productId > 0); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithZeroPeriodPaymentRateMax() { + // Given - maxPeriodPaymentRate can be 0 (zeroOrPositiveAmount) + // Note: periodPaymentRate must be <= max, so we set it to 0 as well + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).withMaxPeriodPaymentRate(BigDecimal.ZERO).withPeriodPaymentRate(BigDecimal.ZERO).build(); + + // When & Then - Should succeed (0 is valid for rate) + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request).getResourceId(); + assertTrue(productId != null && productId > 0); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithNegativePeriodPaymentRateMin() { + // Given - minPeriodPaymentRate must be >= 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withMinPeriodPaymentRate(BigDecimal.valueOf(-1)).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [minPeriodPaymentRate] The parameter `minPeriodPaymentRate` must be greater than or equal to 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithNegativePeriodPaymentRateMax() { + // Given - maxPeriodPaymentRate must be >= 0 if provided + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withMaxPeriodPaymentRate(BigDecimal.valueOf(-1)).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals( + "Validation errors: [maxPeriodPaymentRate] The parameter `maxPeriodPaymentRate` must be greater than or equal to 0.; [periodPaymentRate] Failed data validation due to: must.be.less.than.or.equal.to.max.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingPeriodPaymentRateDefault() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withPeriodPaymentRate(null) + .build(); + request.setPeriodPaymentRate(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [periodPaymentRate] The parameter `periodPaymentRate` is mandatory.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithNegativePeriodPaymentRateDefault() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() + .withPeriodPaymentRate(BigDecimal.valueOf(-1.0)).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [periodPaymentRate] The parameter `periodPaymentRate` must be greater than or equal to 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingPeriodPaymentFrequency() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withRepaymentEvery(null).build(); + request.setRepaymentEvery(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [repaymentEvery] The parameter `repaymentEvery` is mandatory.", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidPeriodPaymentFrequency() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withRepaymentEvery(0).build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [repaymentEvery] The parameter `repaymentEvery` must be greater than 0.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingPeriodPaymentFrequencyType() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder().withRepaymentFrequencyType(null) + .build(); + request.setRepaymentFrequencyType(null); // Explicitly set to null + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [repaymentFrequencyType] The parameter `repaymentFrequencyType` is mandatory.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMissingPaymentAllocationTypes() { + // Given + final PostWorkingCapitalLoanProductsRequest baseRequest = new WorkingCapitalLoanProductTestBuilder().build(); + // Set paymentAllocation with empty paymentAllocationOrder + final PostWorkingCapitalLoanProductsRequest request; + try { + final ObjectMapper objectMapper = ObjectMapperFactory.getShared(); + final String requestJson = objectMapper.writeValueAsString(baseRequest); + final ObjectNode requestNode = (ObjectNode) objectMapper.readTree(requestJson); + final ArrayNode paymentAllocationArray = objectMapper.createArrayNode(); + final ObjectNode paymentAllocationNode = objectMapper.createObjectNode(); + paymentAllocationNode.put("transactionType", "DEFAULT"); + paymentAllocationNode.set("paymentAllocationOrder", objectMapper.createArrayNode()); // Empty array + paymentAllocationArray.add(paymentAllocationNode); + requestNode.set("paymentAllocation", paymentAllocationArray); + request = objectMapper.treeToValue(requestNode, PostWorkingCapitalLoanProductsRequest.class); + } catch (final Exception e) { + throw new IllegalStateException("Failed to set paymentAllocation with empty paymentAllocationOrder", e); + } + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [id] Payment allocation order cannot be empty", exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithInvalidPaymentAllocationType() { + // Given + final PostWorkingCapitalLoanProductsRequest baseRequest = new WorkingCapitalLoanProductTestBuilder().build(); + // Set paymentAllocation with invalid allocation type + final PostWorkingCapitalLoanProductsRequest request; + try { + final ObjectMapper objectMapper = ObjectMapperFactory.getShared(); + final String requestJson = objectMapper.writeValueAsString(baseRequest); + final ObjectNode requestNode = (ObjectNode) objectMapper.readTree(requestJson); + final ArrayNode paymentAllocationArray = objectMapper.createArrayNode(); + final ObjectNode paymentAllocationNode = objectMapper.createObjectNode(); + paymentAllocationNode.put("transactionType", "DEFAULT"); + final ArrayNode paymentAllocationOrderArray = objectMapper.createArrayNode(); + final ObjectNode orderItem = objectMapper.createObjectNode(); + orderItem.put("paymentAllocationRule", "INVALID_TYPE"); + orderItem.put("order", 1); + paymentAllocationOrderArray.add(orderItem); + paymentAllocationNode.set("paymentAllocationOrder", paymentAllocationOrderArray); + paymentAllocationArray.add(paymentAllocationNode); + requestNode.set("paymentAllocation", paymentAllocationArray); + request = objectMapper.treeToValue(requestNode, PostWorkingCapitalLoanProductsRequest.class); + } catch (final Exception e) { + throw new IllegalStateException("Failed to set paymentAllocation with invalid type", e); + } + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals( + "Validation errors: [id] Each provided payment allocation must contain exactly 3 allocation rules, but 1 were provided", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMinGreaterThanMaxPrincipalAmount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withPrincipalAmountMin(BigDecimal.valueOf(1000)) // + .withPrincipalAmountDefault(BigDecimal.valueOf(500)) // + .withPrincipalAmountMax(BigDecimal.valueOf(2000)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] Failed data validation due to: must.be.greater.than.or.equal.to.min.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDefaultLessThanMinPrincipalAmount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withPrincipalAmountMin(BigDecimal.valueOf(1000)) // + .withPrincipalAmountDefault(BigDecimal.valueOf(500)) // + .withPrincipalAmountMax(BigDecimal.valueOf(2000)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] Failed data validation due to: must.be.greater.than.or.equal.to.min.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDefaultGreaterThanMaxPrincipalAmount() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withPrincipalAmountMin(BigDecimal.valueOf(1000)) // + .withPrincipalAmountDefault(BigDecimal.valueOf(3000)) // + .withPrincipalAmountMax(BigDecimal.valueOf(2000)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [principal] Failed data validation due to: must.be.less.than.or.equal.to.max.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithMinGreaterThanMaxPeriodPaymentRate() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withMinPeriodPaymentRate(BigDecimal.valueOf(2.0)) // + .withPeriodPaymentRate(BigDecimal.valueOf(1.0)) // + .withMaxPeriodPaymentRate(BigDecimal.valueOf(3.0)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [periodPaymentRate] Failed data validation due to: must.be.greater.than.or.equal.to.min.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDefaultLessThanMinPeriodPaymentRate() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withMinPeriodPaymentRate(BigDecimal.valueOf(1.0)) // + .withPeriodPaymentRate(BigDecimal.valueOf(0.5)) // + .withMaxPeriodPaymentRate(BigDecimal.valueOf(2.0)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [periodPaymentRate] Failed data validation due to: must.be.greater.than.or.equal.to.min.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDefaultGreaterThanMaxPeriodPaymentRate() { + // Given + final PostWorkingCapitalLoanProductsRequest request = new WorkingCapitalLoanProductTestBuilder() // + .withMinPeriodPaymentRate(BigDecimal.valueOf(0.5)) // + .withPeriodPaymentRate(BigDecimal.valueOf(3.0)) // + .withMaxPeriodPaymentRate(BigDecimal.valueOf(2.0)) // + .build(); + + // When & Then - Should throw CallFailedRuntimeException with status 400 + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request)); + assertEquals(400, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [periodPaymentRate] Failed data validation due to: must.be.less.than.or.equal.to.max.", + exception.getDeveloperMessage()); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDuplicateName() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName1 = "TW" + UUID.randomUUID().toString().substring(0, 2); + final String uniqueShortName2 = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest request1 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName1).build(); + final PostWorkingCapitalLoanProductsRequest request2 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName2).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request1).getResourceId(); + + // When & Then - Duplicate name is a domain rule violation (403) + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request2)); + assertEquals(403, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertTrue(exception.getDeveloperMessage().contains("Validation errors: [id] Working Capital Loan Product with name")); + assertTrue(exception.getDeveloperMessage().contains("already exists")); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDuplicateShortName() { + // Given + final String uniqueName1 = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueName2 = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String shortName = Utils.uniqueRandomStringGenerator("", 4); + final PostWorkingCapitalLoanProductsRequest request1 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName1) + .withShortName(shortName).build(); + final PostWorkingCapitalLoanProductsRequest request2 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName2) + .withShortName(shortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request1).getResourceId(); + + // When & Then - Duplicate shortName is a domain rule violation (403) + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request2)); + assertEquals(403, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertTrue(exception.getDeveloperMessage().contains("Validation errors: [id] Working Capital Loan Product with short name")); + assertTrue(exception.getDeveloperMessage().contains("already exists")); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testCreateWorkingCapitalLoanProductWithDuplicateExternalId() { + // Given + final String uniqueName1 = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueName2 = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName1 = "TW" + UUID.randomUUID().toString().substring(0, 2); + final String uniqueShortName2 = "TW" + UUID.randomUUID().toString().substring(0, 2); + final String externalId = UUID.randomUUID().toString(); + final PostWorkingCapitalLoanProductsRequest request1 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName1) + .withShortName(uniqueShortName1).withExternalId(externalId).build(); + final PostWorkingCapitalLoanProductsRequest request2 = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName2) + .withShortName(uniqueShortName2).withExternalId(externalId).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(request1).getResourceId(); + + // When & Then - Duplicate externalId is a domain rule violation (403) + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.createWorkingCapitalLoanProduct(request2)); + assertEquals(403, exception.getStatus()); + assertNotNull(exception.getDeveloperMessage()); + assertTrue(exception.getDeveloperMessage().contains("Validation errors: [id] Working Capital Loan Product with external id")); + assertTrue(exception.getDeveloperMessage().contains("already exists")); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } + + @Test + public void testUpdateWorkingCapitalLoanProductWithInvalidDateRange() { + // Given + final String uniqueName = "Test wcl Product " + UUID.randomUUID().toString().substring(0, 8); + final String uniqueShortName = "TW" + UUID.randomUUID().toString().substring(0, 2); + final PostWorkingCapitalLoanProductsRequest createRequest = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).build(); + final Long productId = wclProductHelper.createWorkingCapitalLoanProduct(createRequest).getResourceId(); + + // When - update with invalid date range (startDate > closeDate) + final PutWorkingCapitalLoanProductsProductIdRequest updateRequest = new WorkingCapitalLoanProductTestBuilder().withName(uniqueName) + .withShortName(uniqueShortName).buildUpdateRequest(); + updateRequest.setStartDate("2025-12-31"); + updateRequest.setCloseDate("2025-01-01"); + + // Then - Invalid date range is a domain rule violation (400 or 403) + final CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> wclProductHelper.updateWorkingCapitalLoanProductById(productId, updateRequest)); + assertTrue(exception.getStatus() == 400); + assertNotNull(exception.getDeveloperMessage()); + assertEquals("Validation errors: [closeDate] Failed data validation due to: must.be.after.startDate.", + exception.getDeveloperMessage()); + + wclProductHelper.deleteWorkingCapitalLoanProductById(productId); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientSearchTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientSearchTest.java index 456d8d87211..d5af083c344 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientSearchTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientSearchTest.java @@ -323,10 +323,10 @@ public void testClientSearchByLegalForm() { secondEntityClientRequest.setLegalFormId(2L); PostClientsResponse secondEntityClientResponse = clientHelper.createClient(secondEntityClientRequest); // when - GetClientsResponse individualClients = ok(fineractClient().clients.retrieveAll21(newOffice.getOfficeId(), null, null, null, null, - null, null, null, null, null, null, null, 1)); - GetClientsResponse entityClients = ok(fineractClient().clients.retrieveAll21(newOffice.getOfficeId(), null, null, null, null, null, - null, null, null, "id", null, null, 2)); + GetClientsResponse individualClients = ok(fineractClient().clients.retrieveAllClients(newOffice.getOfficeId(), null, null, null, + null, null, null, null, null, null, null, null, 1)); + GetClientsResponse entityClients = ok(fineractClient().clients.retrieveAllClients(newOffice.getOfficeId(), null, null, null, null, + null, null, null, null, "id", null, null, 2)); // then assertThat(individualClients.getTotalFilteredRecords()).isEqualTo(1); assertThat(individualClients.getPageItems().get(0).getId()).isEqualTo(individualClientResponse.getClientId()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTest.java index 182646c7af3..6a1bd64c66b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ClientTest.java @@ -56,14 +56,14 @@ Long create() { // TODO activationDate() why String? https://issues.apache.org/jira/browse/FINERACT-1232 // TODO why dateFormat and locale required even when no activationDate?! // https://issues.apache.org/jira/browse/FINERACT-1233 - return ok(fineractClient().clients.create6( + return ok(fineractClient().clients.createClient( new PostClientsRequest().legalFormId(1L).officeId(1L).fullname("TestClient").dateFormat(Utils.DATE_FORMAT).locale("en_US"))) .getClientId(); } Optional retrieveFirst() { GetClientsResponse clients = ok( - fineractClient().clients.retrieveAll21(null, null, null, null, null, null, null, 0, 1, null, null, false, null)); + fineractClient().clients.retrieveAllClients(null, null, null, null, null, null, null, 0, 1, null, null, false, null)); if (clients.getTotalFilteredRecords() != null && clients.getTotalFilteredRecords() > 0) { return clients.getPageItems().stream().findFirst().map(item -> item.getId()); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/DocumentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/DocumentTest.java index ad87d74abc0..3970aebb71f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/DocumentTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/DocumentTest.java @@ -117,7 +117,7 @@ void deleteDocument() { ok(fineractClient().documents.deleteDocument("clients", clientId, documentId)); CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> ok(fineractClient().documents.getDocument("clients", clientId, documentId))); - assertEquals(403, exception.getResponse().code()); + assertEquals(404, exception.getResponse().code()); } @Order(9999) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignDocumentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignDocumentTest.java index e7585007f11..9819ebeca6d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignDocumentTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignDocumentTest.java @@ -133,7 +133,7 @@ void testDeleteDocument() { FeignException exception = assertThrows(FeignException.class, () -> fineractClient().documentsFixed().getDocument("clients", clientId, documentId)); - assertEquals(403, exception.status()); + assertEquals(404, exception.status()); } @Test diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignImageTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignImageTest.java index 22c7c22a0f9..d75d205618e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignImageTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/FeignImageTest.java @@ -60,7 +60,7 @@ void setupStaff() { request.setDateFormat("yyyy-MM-dd"); request.setLocale("en_US"); - CreateStaffResponse response = ok(() -> fineractClient().staff().create3(request)); + CreateStaffResponse response = ok(() -> fineractClient().staff().createStaff(request)); assertThat(response).isNotNull(); assertThat(response.getResourceId()).isNotNull(); staffId = response.getResourceId(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ImageTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ImageTest.java index a90947c05d7..c27f0e64e75 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ImageTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ImageTest.java @@ -100,7 +100,7 @@ void getInlineOctetOutput() throws IOException { assertImage(body); } - var staff = ok(fineractClient().staff.retrieveOne8(staffId)); + var staff = ok(fineractClient().staff.retrieveOneStaff(staffId)); assertThat(Parts.fileName(r)).hasValue(staff.getDisplayName()); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/IntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/IntegrationTest.java index 072dab57c0b..4031caeb5de 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/IntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/IntegrationTest.java @@ -20,9 +20,14 @@ import java.math.BigDecimal; import java.util.Optional; +import org.apache.fineract.client.models.BusinessDateUpdateRequest; +import org.apache.fineract.client.models.PutGlobalConfigurationsRequest; import org.apache.fineract.client.util.Calls; import org.apache.fineract.client.util.FineractClient; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.FineractClientHelper; +import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.assertj.core.api.AbstractBigDecimalAssert; import org.assertj.core.api.AbstractBooleanAssert; import org.assertj.core.api.AbstractDoubleAssert; @@ -52,6 +57,11 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public abstract class IntegrationTest { + protected static final String DATETIME_PATTERN = "dd MMMM yyyy"; + protected static final String LOCALE = "en"; + protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper(); + protected BusinessDateHelper businessDateHelper = new BusinessDateHelper(); + protected FineractClient fineractClient() { return FineractClientHelper.getFineractClient(); } @@ -118,4 +128,17 @@ public static AbstractStringAssert assertThat(String actual) { public static OptionalAssert assertThat(Optional actual) { return Assertions.assertThat(actual); } + + protected void runAt(String date, Runnable runnable) { + try { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(true)); + businessDateHelper.updateBusinessDate(new BusinessDateUpdateRequest().type(BusinessDateUpdateRequest.TypeEnum.BUSINESS_DATE) + .date(date).dateFormat(DATETIME_PATTERN).locale("en")); + runnable.run(); + } finally { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(false)); + } + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java index 4d3ad68ce1a..4b0adb69cee 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/ReportsTest.java @@ -48,7 +48,7 @@ public void setup() { @Test void listReports() { - assertThat(ok(fineractClient().reports.retrieveReportList())).hasSize(128); + assertThat(ok(fineractClient().reports.retrieveReportList())).hasSize(84); } @Test @@ -83,7 +83,7 @@ void runExpectedPaymentsPentahoReportWithoutPlugin() { () -> ok(fineractClient().reportsRun.runReportGetFile("Expected Payments By Date - Formatted", Map.of("R_endDate", "2013-04-30", "R_loanOfficerId", "-1", "R_officeId", "1", "R_startDate", "2013-04-16", "output-type", "PDF"), false))); - assertEquals(503, exception.getResponse().code()); + assertEquals(404, exception.getResponse().code()); } @Test diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/StaffTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/StaffTest.java index 94f21a103b3..3ada36bfbf4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/StaffTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/StaffTest.java @@ -52,13 +52,13 @@ public Long getStaffId() { } Long create() { - return ok(fineractClient().staff.create3(new StaffRequest().officeId(1L).firstname(Utils.randomFirstNameGenerator()) + return ok(fineractClient().staff.createStaff(new StaffRequest().officeId(1L).firstname(Utils.randomFirstNameGenerator()) .lastname(Utils.randomLastNameGenerator()).externalId(Utils.randomStringGenerator("", 12)) .joiningDate(LocalDate.now(ZoneId.of("UTC")).toString()).dateFormat("yyyy-MM-dd").locale("en_US"))).getResourceId(); } Optional retrieveFirst() { - var staff = ok(fineractClient().staff.retrieveAll16(1L, true, false, "ACTIVE")); + var staff = ok(fineractClient().staff.retrieveAllStaff(1L, true, false, "ACTIVE")); if (!staff.isEmpty()) { return Optional.of((long) staff.get(0).getId()); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignAccountHelper.java index 163e968e406..689423f22ef 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignAccountHelper.java @@ -63,7 +63,7 @@ private Account createAccount(String name, String glCode, String type) { .type(getAccountTypeId(type))// .usage(1); - PostGLAccountsResponse response = ok(() -> fineractClient.generalLedgerAccount().createGLAccount1(request)); + PostGLAccountsResponse response = ok(() -> fineractClient.generalLedgerAccount().createGLAccount(request)); GetGLAccountsResponse account = ok( () -> fineractClient.generalLedgerAccount().retreiveAccount(response.getResourceId(), Collections.emptyMap())); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java index 22141276cf7..8e29a92bfd3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignClientHelper.java @@ -58,11 +58,11 @@ public Long createClient(String activationDate) { } public Long createClient(PostClientsRequest request) { - PostClientsResponse response = ok(() -> fineractClient.clients().create6(request)); + PostClientsResponse response = ok(() -> fineractClient.clients().createClient(request)); return response.getClientId(); } public GetClientsClientIdResponse getClient(Long clientId) { - return ok(() -> fineractClient.clients().retrieveOne11(clientId, Collections.emptyMap())); + return ok(() -> fineractClient.clients().retrieveOneClient(clientId, Collections.emptyMap())); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignExternalEventHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignExternalEventHelper.java new file mode 100644 index 00000000000..fb7f41b5bb7 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignExternalEventHelper.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.client.feign.helpers; + +import static org.apache.fineract.client.feign.util.FeignCalls.ok; + +import java.util.List; +import java.util.Map; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.ExternalEventConfigurationUpdateRequest; +import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse; + +public class FeignExternalEventHelper { + + private final FineractFeignClient fineractClient; + private final InternalExternalEventsApi internalEventsApi; + + public FeignExternalEventHelper(FineractFeignClient fineractClient) { + this.fineractClient = fineractClient; + this.internalEventsApi = fineractClient.create(InternalExternalEventsApi.class); + } + + public void enableBusinessEvent(String eventName) { + ok(() -> fineractClient.externalEventConfiguration().updateExternalEventConfigurations( + new ExternalEventConfigurationUpdateRequest().externalEventConfigurations(Map.of(eventName, true)))); + } + + public void disableBusinessEvent(String eventName) { + ok(() -> fineractClient.externalEventConfiguration().updateExternalEventConfigurations( + new ExternalEventConfigurationUpdateRequest().externalEventConfigurations(Map.of(eventName, false)))); + } + + public List getExternalEventsByType(String type) { + return ok(() -> internalEventsApi.getAllExternalEvents(Map.of("type", type))); + } + + public void deleteAllExternalEvents() { + ok(() -> { + internalEventsApi.deleteAllExternalEvents(); + return null; + }); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignGlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignGlobalConfigurationHelper.java index a2ae1f1cc87..4b6cc45e698 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignGlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignGlobalConfigurationHelper.java @@ -44,7 +44,7 @@ public void disableOriginatorCreationDuringLoanApplication() { public void updateConfigurationByName(String configName, boolean enabled) { Long configId = getConfigurationIdByName(configName); - ok(() -> fineractClient.globalConfiguration().updateConfiguration1(configId, + ok(() -> fineractClient.globalConfiguration().updateGlobalConfiguration(configId, new PutGlobalConfigurationsRequest().enabled(enabled))); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignJournalEntryHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignJournalEntryHelper.java index 3f1416006bd..faf7bf77ce3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignJournalEntryHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignJournalEntryHelper.java @@ -38,7 +38,7 @@ public FeignJournalEntryHelper(FineractFeignClient fineractClient) { } public GetJournalEntriesTransactionIdResponse getJournalEntriesForLoan(Long loanId) { - return ok(() -> fineractClient.journalEntries().retrieveAll1(Map.of("loanId", loanId))); + return ok(() -> fineractClient.journalEntries().retrieveAllJournalEntries(Map.of("loanId", loanId))); } public void verifyJournalEntries(Long loanId, LoanTestData.Journal... expectedEntries) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java index 59f2089c15d..ba52f07d0a1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignLoanOriginatorHelper.java @@ -49,24 +49,24 @@ public Long createOriginator(String externalId, String name, String status) { } public Long createOriginator(PostLoanOriginatorsRequest request) { - PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().create11(request)); + PostLoanOriginatorsResponse response = ok(() -> fineractClient.loanOriginators().createLoanOriginator(request)); return response.getResourceId(); } public CallFailedRuntimeException createOriginatorExpectingError(PostLoanOriginatorsRequest request) { - return fail(() -> fineractClient.loanOriginators().create11(request)); + return fail(() -> fineractClient.loanOriginators().createLoanOriginator(request)); } public List getAllOriginators() { - return ok(() -> fineractClient.loanOriginators().retrieveAll28()); + return ok(() -> fineractClient.loanOriginators().retrieveAllLoanOriginators()); } public GetLoanOriginatorsResponse getOriginatorById(Long originatorId) { - return ok(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + return ok(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); } public CallFailedRuntimeException getOriginatorByIdExpectingError(Long originatorId) { - return fail(() -> fineractClient.loanOriginators().retrieveOne18(originatorId)); + return fail(() -> fineractClient.loanOriginators().retrieveOneLoanOriginator(originatorId)); } public GetLoanOriginatorsResponse getOriginatorByExternalId(String externalId) { @@ -78,7 +78,7 @@ public CallFailedRuntimeException getOriginatorByExternalIdExpectingError(String } public PutLoanOriginatorsResponse updateOriginator(Long originatorId, PutLoanOriginatorsRequest request) { - return ok(() -> fineractClient.loanOriginators().update16(originatorId, request)); + return ok(() -> fineractClient.loanOriginators().updateLoanOriginator(originatorId, request)); } public PutLoanOriginatorsResponse updateOriginatorByExternalId(String externalId, PutLoanOriginatorsRequest request) { @@ -86,11 +86,11 @@ public PutLoanOriginatorsResponse updateOriginatorByExternalId(String externalId } public CallFailedRuntimeException updateOriginatorExpectingError(Long originatorId, PutLoanOriginatorsRequest request) { - return fail(() -> fineractClient.loanOriginators().update16(originatorId, request)); + return fail(() -> fineractClient.loanOriginators().updateLoanOriginator(originatorId, request)); } public Long deleteOriginator(Long originatorId) { - var response = ok(() -> fineractClient.loanOriginators().delete14(originatorId)); + var response = ok(() -> fineractClient.loanOriginators().deleteLoanOriginator(originatorId)); return response.getResourceId(); } @@ -100,7 +100,7 @@ public Long deleteOriginatorByExternalId(String externalId) { } public CallFailedRuntimeException deleteOriginatorExpectingError(Long originatorId) { - return fail(() -> fineractClient.loanOriginators().delete14(originatorId)); + return fail(() -> fineractClient.loanOriginators().deleteLoanOriginator(originatorId)); } public static String generateUniqueExternalId() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java index 0528639f7ba..25f1beafe0e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/FeignSchedulerHelper.java @@ -51,7 +51,7 @@ public void startScheduler() { public void executeAndAwaitJob(String jobDisplayName) { stopScheduler(); - List allJobs = ok(() -> fineractClient.schedulerJob().retrieveAll8()); + List allJobs = ok(() -> fineractClient.schedulerJob().retrieveAllSchedulerJobs()); GetJobsResponse targetJob = allJobs.stream().filter(j -> jobDisplayName.equals(j.getDisplayName())).findFirst() .orElseThrow(() -> new RuntimeException("Job not found: " + jobDisplayName)); @@ -59,7 +59,7 @@ public void executeAndAwaitJob(String jobDisplayName) { FeignCalls.executeVoid(() -> fineractClient.schedulerJob().executeJob(targetJob.getJobId(), "executeJob", new ExecuteJobRequest())); Awaitility.await().atMost(Duration.ofMinutes(2)).pollInterval(Duration.ofSeconds(1)).pollDelay(Duration.ofSeconds(1)).until(() -> { - GetJobsResponse job = ok(() -> fineractClient.schedulerJob().retrieveOne5(targetJob.getJobId())); + GetJobsResponse job = ok(() -> fineractClient.schedulerJob().retrieveOneSchedulerJob(targetJob.getJobId())); JobDetailHistoryData history = job.getLastRunHistory(); if (history == null || history.getJobRunStartTime() == null) { return false; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/InternalExternalEventsApi.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/InternalExternalEventsApi.java new file mode 100644 index 00000000000..5a118085f6d --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/helpers/InternalExternalEventsApi.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.client.feign.helpers; + +import feign.Headers; +import feign.QueryMap; +import feign.RequestLine; +import java.util.List; +import java.util.Map; +import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse; + +/** + * Feign interface for the internal-only external events API. This endpoint is only available when the server runs with + * the TEST profile and is not part of the generated OpenAPI client. Check InternalExternalEventsApiResource.java for + * the server-side implementation. + */ +@Headers({ "Accept: application/json", "Content-Type: application/json" }) +public interface InternalExternalEventsApi { + + @RequestLine("GET /v1/internal/externalevents") + List getAllExternalEvents(@QueryMap(encoded = true) Map queryParams); + + @RequestLine("DELETE /v1/internal/externalevents") + void deleteAllExternalEvents(); +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanChargeOriginatorEnricherTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanChargeOriginatorEnricherTest.java new file mode 100644 index 00000000000..793de4fdd80 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignLoanChargeOriginatorEnricherTest.java @@ -0,0 +1,196 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.client.feign.tests; + +import java.util.List; +import java.util.Map; +import org.apache.fineract.client.feign.FineractFeignClient; +import org.apache.fineract.client.models.ChargeRequest; +import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest; +import org.apache.fineract.infrastructure.event.external.data.ExternalEventResponse; +import org.apache.fineract.integrationtests.client.FeignIntegrationTest; +import org.apache.fineract.integrationtests.client.feign.helpers.FeignClientHelper; +import org.apache.fineract.integrationtests.client.feign.helpers.FeignExternalEventHelper; +import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanHelper; +import org.apache.fineract.integrationtests.client.feign.helpers.FeignLoanOriginatorHelper; +import org.apache.fineract.integrationtests.common.FineractFeignClientHelper; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class FeignLoanChargeOriginatorEnricherTest extends FeignIntegrationTest { + + private static final String ADD_CHARGE_EVENT = "LoanAddChargeBusinessEvent"; + + private static FineractFeignClient fineractClient; + private static FeignLoanOriginatorHelper originatorHelper; + private static FeignClientHelper clientHelper; + private static FeignLoanHelper loanHelper; + private static FeignExternalEventHelper externalEventHelper; + + @BeforeAll + public static void setup() { + fineractClient = FineractFeignClientHelper.getFineractFeignClient(); + originatorHelper = new FeignLoanOriginatorHelper(fineractClient); + clientHelper = new FeignClientHelper(fineractClient); + loanHelper = new FeignLoanHelper(fineractClient); + externalEventHelper = new FeignExternalEventHelper(fineractClient); + } + + @Test + public void testLoanAddChargeEventContainsOriginators() { + externalEventHelper.enableBusinessEvent(ADD_CHARGE_EVENT); + try { + // Given: a loan with an originator attached + final String originatorExternalId = FeignLoanOriginatorHelper.generateUniqueExternalId(); + final Long originatorId = originatorHelper.createOriginator(originatorExternalId, "Test Originator", "ACTIVE"); + final Long clientId = clientHelper.createClient(); + final Long loanId = loanHelper.createSubmittedLoan(clientId); + originatorHelper.attachOriginatorToLoan(loanId, originatorId); + + final Long chargeId = createFlatFeeCharge(50.0); + + externalEventHelper.deleteAllExternalEvents(); + + // When: a charge is added to the loan + ok(() -> fineractClient.loanCharges() + .executeLoanCharge(loanId, + new PostLoansLoanIdChargesRequest().chargeId(chargeId).amount(50.0).locale("en").dateFormat("dd MMMM yyyy") + .dueDate(org.apache.fineract.integrationtests.common.Utils.dateFormatter + .format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant())), + (String) null)); + + // Then: the external event payload contains originator details + final List events = externalEventHelper.getExternalEventsByType(ADD_CHARGE_EVENT); + assertThat(events).isNotEmpty(); + + final ExternalEventResponse event = events.stream().filter(e -> loanId.equals(extractLoanId(e))).findFirst().orElse(null); + assertThat(event).isNotNull(); + + final Object originators = event.getPayLoad().get("originators"); + assertThat(originators).isNotNull().isInstanceOf(List.class); + + @SuppressWarnings("unchecked") + final List> originatorList = (List>) originators; + assertThat(originatorList).hasSize(1); + assertThat(originatorList.get(0).get("externalId")).isEqualTo(originatorExternalId); + + // Cleanup + originatorHelper.detachOriginatorFromLoan(loanId, originatorId); + originatorHelper.deleteOriginator(originatorId); + } finally { + externalEventHelper.disableBusinessEvent(ADD_CHARGE_EVENT); + } + } + + @Test + public void testLoanAddChargeEventContainsMultipleOriginators() { + externalEventHelper.enableBusinessEvent(ADD_CHARGE_EVENT); + try { + // Given: a loan with two originators attached + final String externalId1 = FeignLoanOriginatorHelper.generateUniqueExternalId(); + final String externalId2 = FeignLoanOriginatorHelper.generateUniqueExternalId(); + final Long originatorId1 = originatorHelper.createOriginator(externalId1, "Originator One", "ACTIVE"); + final Long originatorId2 = originatorHelper.createOriginator(externalId2, "Originator Two", "ACTIVE"); + final Long clientId = clientHelper.createClient(); + final Long loanId = loanHelper.createSubmittedLoan(clientId); + originatorHelper.attachOriginatorToLoan(loanId, originatorId1); + originatorHelper.attachOriginatorToLoan(loanId, originatorId2); + + final Long chargeId = createFlatFeeCharge(75.0); + + externalEventHelper.deleteAllExternalEvents(); + + // When: a charge is added + ok(() -> fineractClient.loanCharges() + .executeLoanCharge(loanId, + new PostLoansLoanIdChargesRequest().chargeId(chargeId).amount(75.0).locale("en").dateFormat("dd MMMM yyyy") + .dueDate(org.apache.fineract.integrationtests.common.Utils.dateFormatter + .format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant())), + (String) null)); + + // Then: both originators appear in the event + final List events = externalEventHelper.getExternalEventsByType(ADD_CHARGE_EVENT); + final ExternalEventResponse event = events.stream().filter(e -> loanId.equals(extractLoanId(e))).findFirst().orElse(null); + assertThat(event).isNotNull(); + + @SuppressWarnings("unchecked") + final List> originatorList = (List>) event.getPayLoad().get("originators"); + assertThat(originatorList).hasSize(2); + + // Cleanup + originatorHelper.detachOriginatorFromLoan(loanId, originatorId1); + originatorHelper.detachOriginatorFromLoan(loanId, originatorId2); + originatorHelper.deleteOriginator(originatorId1); + originatorHelper.deleteOriginator(originatorId2); + } finally { + externalEventHelper.disableBusinessEvent(ADD_CHARGE_EVENT); + } + } + + @Test + public void testLoanAddChargeEventWithNoOriginators() { + externalEventHelper.enableBusinessEvent(ADD_CHARGE_EVENT); + try { + // Given: a loan without originators + final Long clientId = clientHelper.createClient(); + final Long loanId = loanHelper.createSubmittedLoan(clientId); + + final Long chargeId = createFlatFeeCharge(50.0); + + externalEventHelper.deleteAllExternalEvents(); + + // When: a charge is added + ok(() -> fineractClient.loanCharges() + .executeLoanCharge(loanId, + new PostLoansLoanIdChargesRequest().chargeId(chargeId).amount(50.0).locale("en").dateFormat("dd MMMM yyyy") + .dueDate(org.apache.fineract.integrationtests.common.Utils.dateFormatter + .format(org.apache.fineract.integrationtests.common.Utils.getLocalDateOfTenant())), + (String) null)); + + // Then: originators field is null in the event (no enrichment when loan has no originators) + final List events = externalEventHelper.getExternalEventsByType(ADD_CHARGE_EVENT); + final ExternalEventResponse event = events.stream().filter(e -> loanId.equals(extractLoanId(e))).findFirst().orElse(null); + assertThat(event).isNotNull(); + assertThat(event.getPayLoad().get("originators")).isNull(); + } finally { + externalEventHelper.disableBusinessEvent(ADD_CHARGE_EVENT); + } + } + + private Long createFlatFeeCharge(double amount) { + return ok(() -> fineractClient.charges().createCharge(new ChargeRequest()// + .name("Originator Test Fee " + System.currentTimeMillis())// + .currencyCode("USD")// + .chargeAppliesTo(1)// + .chargeTimeType(2)// + .chargeCalculationType(1)// + .chargePaymentMode(0)// + .amount(amount)// + .active(true)// + .locale("en"))).getResourceId(); + } + + private Long extractLoanId(ExternalEventResponse event) { + final Object loanId = event.getPayLoad().get("loanId"); + if (loanId instanceof Number) { + return ((Number) loanId).longValue(); + } + return null; + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignTrialBalanceSummaryReportTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignTrialBalanceSummaryReportTest.java index a33dfcd0b9e..10cd5730bd1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignTrialBalanceSummaryReportTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/client/feign/tests/FeignTrialBalanceSummaryReportTest.java @@ -125,11 +125,12 @@ public void tearDown() { List currentMappings = ok( () -> fineractClient().mappingFinancialActivitiesToAccounts().retrieveAll()); for (GetFinancialActivityAccountsResponse mapping : currentMappings) { - executeVoid(() -> fineractClient().mappingFinancialActivitiesToAccounts().deleteGLAccount(mapping.getId())); + executeVoid(() -> fineractClient().mappingFinancialActivitiesToAccounts() + .deleteGLAccountMappingFinancialActivityAccount(mapping.getId())); } for (GetFinancialActivityAccountsResponse mapping : originalFinancialMappings) { ok(() -> fineractClient().mappingFinancialActivitiesToAccounts() - .createGLAccount(new PostFinancialActivityAccountsRequest() + .createGLAccountMappingFinancialActivityAccount(new PostFinancialActivityAccountsRequest() .financialActivityId(Long.valueOf(mapping.getFinancialActivityData().getId())) .glAccountId(mapping.getGlAccountData().getId()))); } @@ -146,7 +147,7 @@ public void testReportReturnsExpectedColumns() { assertFalse(response.getColumnHeaders().isEmpty()); List expectedColumns = List.of("postingdate", "product", "glacct", "description", "assetowner", "beginningbalance", - "debitmovement", "creditmovement", "endingbalance"); + "debitmovement", "creditmovement", "endingbalance", "originators"); List actualColumns = response.getColumnHeaders().stream().map(ResultsetColumnHeaderData::getColumnName).toList(); Assertions.assertEquals(expectedColumns, actualColumns); } @@ -263,9 +264,10 @@ private void setupFinancialActivityMapping() { List mappings = ok( () -> fineractClient().mappingFinancialActivitiesToAccounts().retrieveAll()); for (GetFinancialActivityAccountsResponse mapping : mappings) { - executeVoid(() -> fineractClient().mappingFinancialActivitiesToAccounts().deleteGLAccount(mapping.getId())); + executeVoid(() -> fineractClient().mappingFinancialActivitiesToAccounts() + .deleteGLAccountMappingFinancialActivityAccount(mapping.getId())); } - ok(() -> fineractClient().mappingFinancialActivitiesToAccounts().createGLAccount( + ok(() -> fineractClient().mappingFinancialActivitiesToAccounts().createGLAccountMappingFinancialActivityAccount( new PostFinancialActivityAccountsRequest().financialActivityId(100L).glAccountId((long) transferAccount.getAccountID()))); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CenterDomain.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CenterDomain.java index d882b733a1e..d757ec92fe0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CenterDomain.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CenterDomain.java @@ -110,8 +110,8 @@ public String toJSON() { return new Gson().toJson(this); } - public static CurrencyDomain fromJSON(final String jsonData) { - return new Gson().fromJson(jsonData, CurrencyDomain.class); + public static CenterDomain fromJSON(final String jsonData) { + return new Gson().fromJson(jsonData, CenterDomain.class); } public static Builder create(final Integer id, final Integer statusid, final String statuscode, final String statusvalue, diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java index 5423b24240d..61fe8c01aac 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ClientHelper.java @@ -108,7 +108,7 @@ public static Integer createClient(final RequestSpecification requestSpec, final } public static PostClientsResponse createClient(final PostClientsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().clients.create6(request)); + return Calls.ok(FineractClientHelper.getFineractClient().clients.createClient(request)); } public PostClientsClientIdIdentifiersResponse createClientIdentifer(final Long clientId, @@ -1134,31 +1134,32 @@ public static Boolean getClientTransactions(final RequestSpecification requestSp } public GetClientsClientIdTransactionsResponse getAllClientTransactionsByExternalId(final String externalId) { - return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.retrieveAllClientTransactions1(externalId, 0, 100)); + return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions + .retrieveAllClientTransactionsByClientExternalId(externalId, 0, 100)); } public GetClientsClientIdTransactionsTransactionIdResponse getClientTransactionByExternalId(final String externalId, final String transactionId) { - return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.retrieveClientTransaction2(externalId, + return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.retrieveClientTransactionByClientExternalId(externalId, Long.parseLong(transactionId))); } public GetClientsClientIdTransactionsTransactionIdResponse getClientTransactionByTransactionExternalId(final Long clientId, final String transactionExternalId) { - return Calls.ok( - FineractClientHelper.getFineractClient().clientTransactions.retrieveClientTransaction1(clientId, transactionExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions + .retrieveClientTransactionByTransactionExternalId(clientId, transactionExternalId)); } public PostClientsClientIdTransactionsTransactionIdResponse undoClientTransactionByExternalId(final String externalId, final String transactionId) { - return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.undoClientTransaction2(externalId, + return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.undoClientTransactionByClientExternalId(externalId, Long.parseLong(transactionId), "undo")); } public PostClientsClientIdTransactionsTransactionIdResponse undoClientTransactionByTransactionExternalId(final Long clientId, final String transactionExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.undoClientTransaction1(clientId, transactionExternalId, - "undo")); + return Calls.ok(FineractClientHelper.getFineractClient().clientTransactions.undoClientTransactionByTransactionExternalId(clientId, + transactionExternalId, "undo")); } // TODO: Rewrite to use fineract-client instead! diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauConfigurationHelper.java index df5c3084db7..8aee572a3b7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauConfigurationHelper.java @@ -19,214 +19,68 @@ package org.apache.fineract.integrationtests.common; import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.restassured.path.json.JsonPath; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.apache.fineract.client.services.CreditBureauConfigurationApi; import org.apache.fineract.client.util.Calls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CreditBureauConfigurationHelper { +public final class CreditBureauConfigurationHelper { - private static final String CREATE_CREDITBUREAUCONFIGURATION_URL = "/fineract-provider/api/v1/CreditBureauConfiguration/configuration?" - + Utils.TENANT_IDENTIFIER; private static final Logger LOG = LoggerFactory.getLogger(CreditBureauConfigurationHelper.class); - private final RequestSpecification requestSpec; - private final ResponseSpecification responseSpec; - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public CreditBureauConfigurationHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { - this.requestSpec = requestSpec; - this.responseSpec = responseSpec; - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static List> getCreditBureauConfiguration(RequestSpecification requestSpec, - ResponseSpecification responseSpec, String creditBureauId) { - LOG.info("---------------------------------GET A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/CreditBureauConfiguration/config/" + creditBureauId + "?" - + Utils.TENANT_IDENTIFIER; - return JsonPath.from(Utils.performServerGet(requestSpec, responseSpec, CREDITBUREAU_CONFIGURATION_URL)).getList(""); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Integer createCreditBureauConfiguration(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - String configKey) { - return createCreditBureauConfiguration(requestSpec, responseSpec, "1", configKey); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Integer createCreditBureauConfiguration(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, String configKey, String value, String description) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/CreditBureauConfiguration/configuration/" + creditBureauId - + "?" + Utils.TENANT_IDENTIFIER; - return Utils.performServerPost(requestSpec, responseSpec, CREDITBUREAU_CONFIGURATION_URL, - creditBureauConfigurationAsJson(configKey, value, description), "resourceId"); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Integer createCreditBureauConfiguration(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, String configKey) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - return createCreditBureauConfiguration(requestSpec, responseSpec, creditBureauId, configKey, "testConfigKeyValue", "description"); - } - - /* - * public static Object updateCreditBureauConfiguration(final RequestSpecification requestSpec, final - * ResponseSpecification responseSpec, final Integer ConfigurationId) { return - * updateCreditBureauConfiguration(requestSpec, responseSpec, ConfigurationId); } - */ - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String updateCreditBureauConfiguration(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final Integer ConfigurationId) { - - Object configurationObject = updateCreditBureauConfiguration(requestSpec, responseSpec, ConfigurationId, null, - "updateConfigKeyValue"); - // Convert the Object to String and fetch updated value - Gson gson = new Gson(); - String result = gson.toJson(configurationObject); - JsonObject reportObject = JsonParser.parseString(result).getAsJsonObject(); - String value = reportObject.get("value").getAsString(); - - return value; - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Object updateCreditBureauConfiguration(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final Integer ConfigurationId, String configKey, final String updateConfigKeyValue) { - LOG.info("---------------------------------UPDATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/CreditBureauConfiguration/configuration/" + ConfigurationId - + "?" + Utils.TENANT_IDENTIFIER; - return Utils.performServerPut(requestSpec, responseSpec, CREDITBUREAU_CONFIGURATION_URL, - updateCreditBureauConfigurationAsJson(configKey, updateConfigKeyValue), "changes"); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Object getOrganizationCreditBureauConfiguration(final RequestSpecification requestSpec, - final ResponseSpecification responseSpec) { - LOG.info("---------------------------------GETTING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/CreditBureauConfiguration/organisationCreditBureau?" - + Utils.TENANT_IDENTIFIER; - return Utils.performServerGet(requestSpec, responseSpec, CREDITBUREAU_CONFIGURATION_URL, null); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Object addOrganisationCreditBureau(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, String alias, boolean isActive) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String URL = "/fineract-provider/api/v1/CreditBureauConfiguration/organisationCreditBureau/" + creditBureauId + "?" - + Utils.TENANT_IDENTIFIER; - return Utils.performServerPost(requestSpec, responseSpec, URL, addOrganizationCreditBureauCreateAsJson(alias, isActive), null); - } + private CreditBureauConfigurationHelper() {} - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Object updateOrganisationCreditBureau(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, boolean isActive) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String URL = "/fineract-provider/api/v1/CreditBureauConfiguration/organisationCreditBureau?" + Utils.TENANT_IDENTIFIER; - return Utils.performServerPut(requestSpec, responseSpec, URL, updateOrganizationCreditBureauCreateAsJson(creditBureauId, isActive), - null); + private static CreditBureauConfigurationApi api() { + return FineractClientHelper.getFineractClient().creditBureauConfiguration; } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String addOrganizationCreditBureauCreateAsJson(final String alias, final boolean isActive) { - final HashMap map = new HashMap<>(); - map.put("alias", alias); - map.put("isActive", isActive); - LOG.info("map : {}", map); - return new Gson().toJson(map); + public static String getOrganisationCreditBureauConfiguration() { + LOG.info( + "---------------------------------GET ORGANISATION CREDIT BUREAU CONFIGURATION---------------------------------------------"); + return Calls.ok(api().getOrganisationCreditBureau()); } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String updateOrganizationCreditBureauCreateAsJson(final String creditBureauId, final boolean isActive) { - final HashMap map = new HashMap<>(); - map.put("creditBureauId", creditBureauId); - map.put("isActive", isActive); - LOG.info("map : {}", map); - return new Gson().toJson(map); + public static String getCreditBureauConfiguration(Long organisationCreditBureauId) { + LOG.info("---------------------------------GET CREDIT BUREAU CONFIGURATION---------------------------------------------"); + return Calls.ok(api().getConfiguration(organisationCreditBureauId)); } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String creditBureauConfigurationAsJson(final String configkey, final String value, final String description) { + public static String createCreditBureauConfiguration(Long creditBureauId, String configKey, String value, String description) { + LOG.info("---------------------------------CREATING A CREDIT BUREAU CONFIGURATION---------------------------------------------"); final HashMap map = new HashMap<>(); - map.put("configkey", configkey); + map.put("configkey", configKey); map.put("value", value); map.put("description", description); - LOG.info("map : {}", map); - return new Gson().toJson(map); + return Calls.ok(api().createCreditBureauConfiguration(creditBureauId, new Gson().toJson(map))); } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String updateCreditBureauConfigurationAsJson(final String configKey, final String value) { + public static String updateCreditBureauConfiguration(Long configurationId, String configKey, String value) { + LOG.info("---------------------------------UPDATING A CREDIT BUREAU CONFIGURATION---------------------------------------------"); final HashMap map = new HashMap<>(); if (configKey != null) { map.put("configkey", configKey); } map.put("value", value); - LOG.info("map : {}", map); - return new Gson().toJson(map); - } - - private static CreditBureauConfigurationApi api() { - return FineractClientHelper.getFineractClient().creditBureauConfiguration; + return Calls.ok(api().updateCreditBureauConfiguration(configurationId, new Gson().toJson(map))); } public static String addOrganisationCreditBureau(Long creditBureauId, String alias, boolean isActive) { + LOG.info("---------------------------------CREATING ORGANISATION CREDIT BUREAU---------------------------------------------"); final HashMap map = new HashMap<>(); map.put("alias", alias); map.put("isActive", isActive); return Calls.ok(api().addOrganisationCreditBureau(creditBureauId, new Gson().toJson(map))); } + public static String updateOrganisationCreditBureau(String creditBureauId, boolean isActive) { + LOG.info("---------------------------------UPDATING ORGANISATION CREDIT BUREAU---------------------------------------------"); + final HashMap map = new HashMap<>(); + map.put("creditBureauId", creditBureauId); + map.put("isActive", isActive); + return Calls.ok(api().updateCreditBureau(new Gson().toJson(map))); + } + public static String createCreditBureauConfigurationRaw(Long creditBureauId, String jsonBody) { return Calls.ok(api().createCreditBureauConfiguration(creditBureauId, jsonBody)); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauIntegrationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauIntegrationHelper.java index 974f25bb255..bc3223025aa 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauIntegrationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CreditBureauIntegrationHelper.java @@ -18,64 +18,29 @@ */ package org.apache.fineract.integrationtests.common; -import static io.restassured.RestAssured.given; - -import com.google.gson.Gson; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; -import java.io.File; import java.util.HashMap; +import org.apache.fineract.client.services.CreditBureauIntegrationApi; +import org.apache.fineract.client.util.Calls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CreditBureauIntegrationHelper { +public final class CreditBureauIntegrationHelper { private static final Logger LOG = LoggerFactory.getLogger(CreditBureauIntegrationHelper.class); - private final RequestSpecification requestSpec; - private final ResponseSpecification responseSpec; - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public CreditBureauIntegrationHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { - this.requestSpec = requestSpec; - this.responseSpec = responseSpec; - } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static Object getCreditReport(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, String nrc) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/creditBureauIntegration/creditReport?" - + Utils.TENANT_IDENTIFIER; - return Utils.performServerPost(requestSpec, responseSpec, CREDITBUREAU_CONFIGURATION_URL, - createGetCreditReportAsJson(creditBureauId, nrc), null); - } + private CreditBureauIntegrationHelper() {} - public static String uploadCreditReport(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String creditBureauId, File file) { - LOG.info("---------------------------------CREATING A CREDIT_BUREAU_CONFIGURATION---------------------------------------------"); - final String CREDITBUREAU_CONFIGURATION_URL = "/fineract-provider/api/v1/creditBureauIntegration/addCreditReport?" - + Utils.TENANT_IDENTIFIER; - return given().spec(requestSpec).queryParam("creditBureauId", creditBureauId).contentType("multipart/form-data") - .multiPart("file", file).expect().spec(responseSpec).log().ifError().when().post(CREDITBUREAU_CONFIGURATION_URL).andReturn() - .asString(); + private static CreditBureauIntegrationApi api() { + return FineractClientHelper.getFineractClient().creditBureauIntegration; } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static String createGetCreditReportAsJson(final String creditBureauId, final String nrc) { + public static String getCreditReport(final String creditBureauId, final String nrc) { + LOG.info("---------------------------------FETCHING CREDIT REPORT---------------------------------------------"); final HashMap map = new HashMap<>(); map.put("creditBureauID", creditBureauId); map.put("NRC", nrc); LOG.info("map : {}", map); - return new Gson().toJson(map); + return Calls.ok(api().fetchCreditReport((Object) map)); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java deleted file mode 100644 index 528bf59966f..00000000000 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrenciesHelper.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.integrationtests.common; - -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import io.restassured.specification.RequestSpecification; -import io.restassured.specification.ResponseSpecification; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@SuppressWarnings({ "unused", "rawtypes", "unchecked" }) -public final class CurrenciesHelper { - - private CurrenciesHelper() { - - } - - private static final Logger LOG = LoggerFactory.getLogger(CurrenciesHelper.class); - private static final String CURRENCIES_URL = "/fineract-provider/api/v1/currencies"; - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static List getAllCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) { - LOG.info("------------------------ RETRIEVING ALL CURRENCIES -------------------------"); - HashMap response = Utils.performServerGet(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER, ""); - var selectedCurrencyOptions = (ArrayList) response.get("selectedCurrencyOptions"); - var currencyOptions = (ArrayList) response.get("currencyOptions"); - currencyOptions.addAll(selectedCurrencyOptions); - var jsonData = new Gson().toJson(selectedCurrencyOptions); - return new Gson().fromJson(jsonData, new TypeToken>() {}.getType()); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static List getSelectedCurrencies(final RequestSpecification requestSpec, - final ResponseSpecification responseSpec) { - LOG.info("------------------------ RETRIEVING ALL SELECTED CURRENCIES -------------------------"); - HashMap response = Utils.performServerGet(requestSpec, responseSpec, - CURRENCIES_URL + "?fields=selectedCurrencyOptions" + "&" + Utils.TENANT_IDENTIFIER, ""); - var jsonData = new Gson().toJson(response.get("selectedCurrencyOptions")); - return new Gson().fromJson(jsonData, new TypeToken>() {}.getType()); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static CurrencyDomain getCurrencybyCode(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final String code) { - var currencies = getAllCurrencies(requestSpec, responseSpec); - for (var currency : currencies) { - if (currency.getCode().equals(code)) { - return currency; - } - } - return null; - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static List updateSelectedCurrencies(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final List currencies) { - LOG.info( - "---------------------------------UPDATE SELECTED CURRENCIES LIST (deprecated)---------------------------------------------"); - // TODO: this nested "changes" map makes no sense whatsover... in the future just use "currencies" (straight - // forward, no nesting, no complexity) - Map changes = Utils.performServerPut(requestSpec, responseSpec, CURRENCIES_URL + "?" + Utils.TENANT_IDENTIFIER, - currenciesToJSON(currencies), "changes"); - return (List) changes.get("currencies"); - } - - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - private static String currenciesToJSON(final List currencies) { - return new Gson().toJson(Map.of("currencies", currencies)); - } -} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java deleted file mode 100644 index a389e88b4bf..00000000000 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/CurrencyDomain.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.integrationtests.common; - -import com.google.gson.Gson; - -public class CurrencyDomain implements Comparable { - - public static final class Builder { - - private String code; - private String name; - private int decimalPlaces; - private String displaySymbol; - private String nameCode; - private String displayLabel; - - private Builder(final String code, final String name, final int decimalPlaces, final String displaySymbol, final String nameCode, - final String displayLabel) { - this.code = code; - this.name = name; - this.decimalPlaces = decimalPlaces; - this.displaySymbol = displaySymbol; - this.nameCode = nameCode; - this.displayLabel = displayLabel; - } - - public CurrencyDomain build() { - return new CurrencyDomain(this.code, this.name, this.decimalPlaces, this.displaySymbol, this.nameCode, this.displayLabel); - } - } - - private String code; - private String name; - private int decimalPlaces; - private String displaySymbol; - private String nameCode; - private String displayLabel; - - CurrencyDomain() { - - } - - private CurrencyDomain(final String code, final String name, final int decimalPlaces, final String displaySymbol, final String nameCode, - final String displayLabel) { - - this.code = code; - this.name = name; - this.decimalPlaces = decimalPlaces; - this.displaySymbol = displaySymbol; - this.nameCode = nameCode; - this.displayLabel = displayLabel; - } - - public String getCode() { - return this.code; - } - - public int getDecimalPlaces() { - return this.decimalPlaces; - } - - public String getDisplaySymbol() { - return this.displaySymbol; - } - - public String getNameCode() { - return this.nameCode; - } - - public String getDisplayLabel() { - return this.displayLabel; - } - - public String getName() { - return this.name; - } - - public String toJSON() { - return new Gson().toJson(this); - } - - public static CurrencyDomain fromJSON(final String jsonData) { - return new Gson().fromJson(jsonData, CurrencyDomain.class); - } - - public static Builder create(final String code, final String name, final int decimalPlaces, final String displaySymbol, - final String nameCode, final String displayLabel) { - return new Builder(code, name, decimalPlaces, displaySymbol, nameCode, displayLabel); - } - - @Override - public int hashCode() { - int hash = 1; - - if (this.name != null) { - hash += this.name.hashCode(); - } - if (this.code != null) { - hash += this.code.hashCode(); - } - if (this.decimalPlaces >= 0) { - hash += this.decimalPlaces; - } - if (this.displaySymbol != null) { - hash += this.displaySymbol.hashCode(); - } - if (this.nameCode != null) { - hash += this.nameCode.hashCode(); - } - if (this.displayLabel != null) { - hash += this.displayLabel.hashCode(); - } - - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (!(obj instanceof CurrencyDomain)) { - return false; - } - - CurrencyDomain cd = (CurrencyDomain) obj; - - if (this.name.equals(cd.name) && this.code.equals(cd.code) && this.decimalPlaces == cd.decimalPlaces - && this.displaySymbol.equals(cd.displaySymbol) && this.nameCode.equals(cd.nameCode) - && this.displayLabel.equals(cd.displayLabel)) { - return true; - } - return false; - } - - @Override - public int compareTo(CurrencyDomain cd) { - return this.name.compareTo(cd.getName()); - } -} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java index 96edda0fa77..ffd1aa05e61 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalAssetOwnerHelper.java @@ -49,12 +49,14 @@ public PostInitiateTransferResponse initiateTransferByLoanId(Long loanId, String } public void cancelTransferByTransferExternalId(String transferExternalId) { - Calls.ok(FineractClientHelper.getFineractClient().externalAssetOwners.transferRequestWithId1(transferExternalId, "cancel")); + Calls.ok(FineractClientHelper.getFineractClient().externalAssetOwners.transferRequestWithIdByExternalId(transferExternalId, + "cancel")); } public void cancelTransferByTransferExternalIdError(String transferExternalId) { - CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> Calls - .okR(FineractClientHelper.getFineractClient().externalAssetOwners.transferRequestWithId1(transferExternalId, "cancel"))); + CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, + () -> Calls.okR(FineractClientHelper.getFineractClient().externalAssetOwners + .transferRequestWithIdByExternalId(transferExternalId, "cancel"))); assertEquals(403, exception.getResponse().code()); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java index cdd1cdf8ae7..aa13a2e6faa 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java @@ -36,7 +36,8 @@ protected ExternalEventConfigurationHelper() {} + Utils.TENANT_IDENTIFIER; // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // Example: + // org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public static ArrayList> getAllExternalEventConfigurations(RequestSpecification requestSpec, @@ -47,7 +48,8 @@ public static ArrayList> getAllExternalEventConfigurations(R } // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // Example: + // org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public static ArrayList> getDefaultExternalEventConfigurations() { @@ -667,11 +669,17 @@ public static ArrayList> getDefaultExternalEventConfiguratio savingsAccountsStayedLockedBusinessEvent.put("enabled", false); defaults.add(savingsAccountsStayedLockedBusinessEvent); + Map savingsAccountForceWithdrawalBusinessEvent = new HashMap<>(); + savingsAccountForceWithdrawalBusinessEvent.put("type", "SavingsAccountForceWithdrawalBusinessEvent"); + savingsAccountForceWithdrawalBusinessEvent.put("enabled", false); + defaults.add(savingsAccountForceWithdrawalBusinessEvent); + return defaults; } // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // Example: + // org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public static String getExternalEventConfigurationsForUpdateJSON() { @@ -690,7 +698,8 @@ public static String getExternalEventConfigurationsForUpdateJSON() { } // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // Example: + // org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public static Map updateExternalEventConfigurations(RequestSpecification requestSpec, @@ -701,7 +710,8 @@ public static Map updateExternalEventConfigurations(RequestSpec } // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // Example: + // org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public static void resetDefaultConfigurations(RequestSpecification requestSpec, ResponseSpecification responseSpec) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java index a92d47a1f30..dca756b40e0 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java @@ -55,10 +55,11 @@ public GlobalConfigurationPropertyData getGlobalConfigurationByName(final String public GlobalConfigurationPropertyData getGlobalConfigurationById(final Long configId) { log.info("------------------------ RETRIEVING GLOBAL CONFIGURATION BY ID -------------------------"); - return Calls.ok(FineractClientHelper.getFineractClient().globalConfigurations.retrieveOne3(configId)); + return Calls.ok(FineractClientHelper.getFineractClient().globalConfigurations.retrieveOneGlobalConfiguration(configId)); } - // TODO: This is quite a bad pattern and adds a lot of time to individual test executions + // TODO: This is quite a bad pattern and adds a lot of time to individual test + // executions public void resetAllDefaultGlobalConfigurations() { GetGlobalConfigurationsResponse actualGlobalConfigurations = getAllGlobalConfigurations(); @@ -231,6 +232,13 @@ private static ArrayList getAllDefaultGlobalConfigurations() { graceOnPenaltyPostingDefault.put("trapDoor", false); defaults.add(graceOnPenaltyPostingDefault); + HashMap forcePasswordResetOnFirstLoginDefault = new HashMap<>(); + forcePasswordResetOnFirstLoginDefault.put("name", GlobalConfigurationConstants.FORCE_PASSWORD_RESET_ON_FIRST_LOGIN); + forcePasswordResetOnFirstLoginDefault.put("value", 0L); + forcePasswordResetOnFirstLoginDefault.put("enabled", false); + forcePasswordResetOnFirstLoginDefault.put("trapDoor", false); + defaults.add(forcePasswordResetOnFirstLoginDefault); + HashMap savingsInterestPostingCurrentPeriodEndDefault = new HashMap<>(); savingsInterestPostingCurrentPeriodEndDefault.put("name", GlobalConfigurationConstants.SAVINGS_INTEREST_POSTING_CURRENT_PERIOD_END); savingsInterestPostingCurrentPeriodEndDefault.put("value", 0L); @@ -600,6 +608,20 @@ private static ArrayList getAllDefaultGlobalConfigurations() { enableOriginatorCreationDuringLoanApplication.put("trapDoor", false); defaults.add(enableOriginatorCreationDuringLoanApplication); + HashMap forceWithdrawalOnSavingsAccount = new HashMap<>(); + forceWithdrawalOnSavingsAccount.put("name", GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT); + forceWithdrawalOnSavingsAccount.put("value", 0L); + forceWithdrawalOnSavingsAccount.put("enabled", false); + forceWithdrawalOnSavingsAccount.put("trapDoor", false); + defaults.add(forceWithdrawalOnSavingsAccount); + + HashMap forceWithdrawalOnSavingsAccountLimit = new HashMap<>(); + forceWithdrawalOnSavingsAccountLimit.put("name", GlobalConfigurationConstants.FORCE_WITHDRAWAL_ON_SAVINGS_ACCOUNT_LIMIT); + forceWithdrawalOnSavingsAccountLimit.put("value", 0L); + forceWithdrawalOnSavingsAccountLimit.put("enabled", false); + forceWithdrawalOnSavingsAccountLimit.put("trapDoor", false); + defaults.add(forceWithdrawalOnSavingsAccountLimit); + return defaults; } @@ -610,7 +632,7 @@ public PutGlobalConfigurationsResponse updateGlobalConfiguration(final String co public void updateGlobalConfigurationInternal(final String configName, final Long value) { log.info("---------------------------UPDATE VALUE FOR GLOBAL CONFIG (internal) ---------------------------------------"); - Calls.ok(FineractClientHelper.getFineractClient().legacy.updateGlobalConfiguration(configName, value)); + Calls.ok(FineractClientHelper.getFineractClient().legacy.updateInternalGlobalConfiguration(configName, value)); } public void manageConfigurations(final String configurationName, final boolean enabled) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java index 8a9f3a3a9e5..1c306fde127 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/PaymentTypeHelper.java @@ -89,7 +89,7 @@ public PaymentTypeUpdateResponse updatePaymentType(final Long paymentTypeId, public PaymentTypeDeleteResponse deletePaymentType(final Long paymentTypeId) { log.info("-------------------------------DELETING PAYMENT TYPE-------------------------------------------"); - return Calls.ok(FineractClientHelper.getFineractClient().paymentTypes.deleteCode1(paymentTypeId)); + return Calls.ok(FineractClientHelper.getFineractClient().paymentTypes.deleteCodePaymentType(paymentTypeId)); } public static String randomNameGenerator(final String prefix, final int lenOfRandomSuffix) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SurveyHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SurveyHelper.java index 01c92b1d3fe..646caec40db 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SurveyHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SurveyHelper.java @@ -74,11 +74,11 @@ public SurveyData retrieveSurvey(Long surveyId) { } public List retrieveAllSurveys() { - return executeApiCall(() -> surveysApi.fetchAllSurveys1(null), "Failed to retrieve all surveys"); + return executeApiCall(() -> surveysApi.fetchAllSurveys(null), "Failed to retrieve all surveys"); } public List retrieveActiveSurveys() { - return executeApiCall(() -> surveysApi.fetchAllSurveys1(true), "Failed to retrieve active surveys"); + return executeApiCall(() -> surveysApi.fetchAllSurveys(true), "Failed to retrieve active surveys"); } public String updateSurvey(Long surveyId, SurveyData surveyData) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java index 7b3bbea7135..52fe0520481 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java @@ -60,7 +60,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; @@ -96,29 +95,99 @@ public final class Utils { private static final Gson gson = new Gson(); private static final String HEALTH_URL = "/fineract-provider/actuator/health"; - private static final Random r = new Random(); - private static final ConcurrentHashMap> uniqueRandomStringContainer = new ConcurrentHashMap<>(); public static final String SOURCE_SET_NUMBERS_AND_LETTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String SOURCE_SET_NUMBERS = "1234567890"; - private static final List firstNames = List.of("Adam", "Alex", "Andrew", "Anthony", "Arthur", "Benjamin", "Brian", "Brandon", - "Bruce", "Caleb", "Charles", "Christian", "Christopher", "Daniel", "David", "Dennis", "Dominic", "Edward", "Ethan", "Felix", - "Frank", "Gabriel", "George", "Gregory", "Harry", "Henry", "Isaac", "Jack", "Jacob", "James", "Jason", "John", "Jonathan", - "Joseph", "Joshua", "Julian", "Kevin", "Kyle", "Leo", "Liam", "Logan", "Lucas", "Luke", "Marcus", "Mark", "Martin", "Matthew", - "Michael", "Nathan", "Nicholas", "Noah", "Oliver", "Oscar", "Patrick", "Paul", "Peter", "Philip", "Raymond", "Richard", - "Robert", "Ryan", "Samuel", "Scott", "Sean", "Simon", "Stephen", "Steven", "Thomas", "Timothy", "Victor", "William", "Zachary", - "Aaron", "Adrian", "Alan", "Albert", "Allen", "Antonio", "Austin", "Blake", "Cameron", "Carlos", "Colin", "Dylan", "Eric", - "Harrison", "Ian", "Jeremy", "Jordan", "Kevin", "Louis", "Mitchell", "Neil", "Roger", "Trevor"); - - private static final List lastNames = List.of("Anderson", "Armstrong", "Baker", "Barnes", "Bell", "Bennett", "Brooks", "Brown", - "Bryant", "Butler", "Campbell", "Carter", "Clark", "Collins", "Cook", "Cooper", "Cox", "Davis", "Diaz", "Edwards", "Evans", - "Fisher", "Foster", "Garcia", "Gomez", "Gonzalez", "Gray", "Green", "Hall", "Harris", "Hernandez", "Hill", "Howard", "Hughes", - "Jackson", "James", "Jenkins", "Johnson", "Jones", "Kelly", "Kim", "King", "Lee", "Lewis", "Lopez", "Martin", "Martinez", - "Miller", "Mitchell", "Moore", "Morgan", "Morris", "Murphy", "Nelson", "Nguyen", "Parker", "Perez", "Peterson", "Phillips", - "Powell", "Price", "Ramirez", "Reed", "Richardson", "Rivera", "Roberts", "Robinson", "Rodriguez", "Rogers", "Ross", "Russell", - "Sanchez", "Scott", "Smith", "Stewart", "Taylor", "Thomas", "Thompson", "Torres", "Turner", "Walker", "Ward", "Watson", "White", - "Williams", "Wilson", "Wood", "Wright", "Young", "Zhang"); + private static final List firstNames = List.of("Aarav", "Abel", "Abner", "Abram", "Ace", "Aden", "Adler", "Adonis", "Ainsley", + "Alaric", "Alastair", "Alberto", "Alden", "Alecander", "Alessandro", "Alfonso", "Alfredo", "Alistair", "Alonzo", "Amari", + "Ambrose", "Amir", "Ansel", "Archer", "Archie", "Arjun", "Arlo", "Armando", "Arnav", "Aron", "Arturo", "Aryan", "Asa", + "Atticus", "Aubrey", "August", "Augustus", "Aurelio", "Avi", "Axel", "Bailey", "Basil", "Beau", "Beckett", "Benicio", "Benson", + "Blaine", "Bo", "Bode", "Boris", "Bowen", "Brady", "Branson", "Brayan", "Briggs", "Brock", "Brody", "Bronson", "Bryce", "Byron", + "Cade", "Caiden", "Cairo", "Callan", "Callum", "Cannon", "Carlton", "Carmelo", "Casey", "Cassius", "Cedrick", "Chance", + "Channing", "Chase", "Cillian", "Cohen", "Conrad", "Corbin", "Cory", "Cruz", "Dakota", "Dalton", "Dane", "Dario", "Darrell", + "Darwin", "Dash", "Davis", "Deacon", "Deandre", "Declan", "Demetrius", "Denver", "Devin", "Dexter", "Dominik", "Dorian", + "Drake", "Duncan", "Edison", "Elian", "Elias", "Eliezer", "Elisha", "Elmer", "Elon", "Elvis", "Emilio", "Emory", "Ephraim", + "Erik", "Ernest", "Esteban", "Eugene", "Ewan", "Ezra", "Fabian", "Faisal", "Finn", "Fletcher", "Ford", "Franco", "Gael", "Gage", + "Gannon", "Gary", "Gideon", "Gianni", "Giovanni", "Glen", "Grady", "Grayson", "Guillermo", "Gunnar", "Gustav", "Hank", "Harlan", + "Hendrix", "Hugo", "Hunter", "Ibrahim", "Idris", "Ignacio", "Imran", "Indiana", "Ismael", "Israel", "Jace", "Jaime", "Jalen", + "Jamal", "Jax", "Jaxon", "Jay", "Jayce", "Jayden", "Jensen", "Jermaine", "Jett", "Jimmy", "Joaquin", "Johan", "Johnny", "Jonas", + "Jorge", "Josiah", "Juan", "Justice", "Kade", "Kai", "Kaiser", "Kareem", "Karter", "Kash", "Kason", "Keegan", "Keon", "Khalil", + "Kieran", "Knox", "Kobe", "Kody", "Kole", "Korbin", "Kristian", "Kurt", "Kyler", "Lachlan", "Lamar", "Landon", "Langston", + "Lars", "Lawrence", "Layton", "Leandro", "Lee", "Lennon", "Leroy", "Liaman", "Lorenzo", "Luciano", "Luis", "Maddox", "Malachi", + "Marek", "Marioh", "Matias", "Matteo", "Mauricio", "Maverick", "Memphis", "Milan", "Miller", "Mohamed", "Montgomery", "Moses", + "Murphy", "Mustafa", "Nadir", "Nathanael", "Nehemiah", "Nico", "Nikolai", "Noel", "Nova", "Octavio", "Odell", "Odin", "Onyx", + "Orion", "Otis", "Paolo", "Percy", "Phoenix", "Pierce", "Porter", "Prince", "Quincy", "Raiden", "Ramon", "Raphael", "Reid", + "Remington", "Rhett", "Rocco", "Rodrigo", "Royce", "Ruben", "Rudy", "Ryker", "Salvatore", "Santino", "Saul", "Sebastian", + "Sergio", "Shane", "Shiloh", "Silas", "Sincere", "Skyler", "Soren", "Sterling", "Stone", "Sullivan", "Sutton", "Talon", "Tate", + "Tatum", "Thiago", "Tobias", "Tomas", "Trace", "Tucker", "Turner", "Ulises", "Uriah", "Valentin", "Van", "Vance", "Vicente", + "Viktor", "Warren", "Watson", "Weston", "Willis", "Wilson", "Winston", "Wyatt", "Xander", "Yael", "Yahir", "Yusuf", "Zaire", + "Zander", "Zion", "Zayn", "Abdiel", "Aditya", "Akeem", "Alvaro", "Amadeo", "Anders", "Anwar", "Armani", "Ayaan", "Aziel", + "Azriel", "Baker", "Benedict", "Bishop", "Blaise", "Bridger", "Cadeo", "Camilo", "Casen", "Caspian", "Cayson", "Cesar", + "Chandler", "Cortez", "Damari", "Dangelo", "Darioh", "Davion", "Dilan", "Dion", "Eliam", "Elio", "Emiliano", "Enrique", + "Ezekiel", "Farhan", "Fisher", "Gino", "Griffin", "Hamza", "Harley", "Henrik", "Iker", "Ira", "Jabari", "Jair", "Javon", + "Jiraiya", "Kaison", "Kamdyn", "Kasen", "Kendrick", "Kian", "Kye", "Lennox", "Leonidas", "Lucian", "Makai", "Marcelo", + "Marcellus", "Marvin", "Massimo", "Misael", "Moises", "Nasir", "Naveen", "Niklaus", "Nixon", "Omari", "Osiris", "Ozzy", + "Palmer", "Paxton", "Quade", "Rayan", "Reuben", "Ronan", "Roscoe", "Samir", "Santana", "Seven", "Shepherd", "Simeon", "Solomon", + "Thaddeus", "Titan", "Trey", "Tripp", "Ty", "Ulysses", "Valor", "Vihaan", "Wilder", "Zev", "Aarush", "Abhiram", "Adem", + "Adriel", "Aeson", "Ahmad", "Aldo", "Alvaro", "Anakin", "Aramis", "Ares", "Axton", "Baylor", "Benton", "Brax", "Briar", "Bruno", + "Calloway", "Cassian", "Cayson", "Crosby", "Cullen", "Dariel", "Davian", "Dax", "Daxter", "Dior", "Eithan", "Eren", "Eros", + "Evander", "Faris", "Finley", "Gatlin", "Grey", "Hollis", "Izan", "Jovanni", "Kael", "Kairo", "Kellan", "Khaled", "Koa", "Krew", + "Kyrie", "Leif", "Lochlan", "Lucius", "Mael", "Malakai", "Marquez", "Matheo", "Neo", "Nikolas", "Ocean", "Osman", "Raul", + "Reign", "Riven", "Rory", "Rowen", "Sage", "Salem", "Shawnte", "Stellan", "Tadeo", "Teo", "Thierry", "Torin", "Zaid"); + + private static final List lastNames = List.of("Abbott", "Acevedo", "Acosta", "Adams", "Adkins", "Aguilar", "Aguirre", "Albert", + "Alexander", "Alford", "Allen", "Allison", "Alston", "Alvarado", "Alvarez", "Ambrose", "Ames", "Anthony", "Archer", "Arias", + "Arnold", "Ashley", "Atkins", "Atkinson", "Austin", "Avery", "Avila", "Ayala", "Baird", "Baldwin", "Ball", "Ballard", "Banks", + "Barber", "Barker", "Barlow", "Barrett", "Barron", "Barry", "Bartlett", "Barton", "Bass", "Bates", "Battle", "Bauer", "Baxter", + "Beach", "Beard", "Beasley", "Beck", "Becker", "Bellamy", "Bender", "Benjamin", "Benson", "Bentley", "Berg", "Berger", + "Bernard", "Berry", "Best", "Bird", "Bishop", "Black", "Blackburn", "Blackwell", "Blair", "Blake", "Blanchard", "Blankenship", + "Blevins", "Bolton", "Bond", "Bonner", "Booker", "Boone", "Bowen", "Bowers", "Bowman", "Boyd", "Boyle", "Bradford", "Bradley", + "Branch", "Bray", "Brennan", "Brewer", "Bridges", "Briggs", "Bright", "Britt", "Brock", "Brockman", "Brooksbank", "Browning", + "Bruce", "Bryan", "Buck", "Buckley", "Bullock", "Burch", "Burgess", "Burke", "Burnett", "Burns", "Burris", "Bush", "Byers", + "Byrd", "Cabrera", "Cain", "Calderon", "Callahan", "Calhoun", "Camacho", "Cameron", "Campos", "Cannon", "Cantrell", "Cardenas", + "Carey", "Carlson", "Carney", "Carpenter", "Carr", "Carrillo", "Carroll", "Carson", "Carteret", "Case", "Casey", "Castaneda", + "Castillo", "Castro", "Cervantes", "Chambers", "Chan", "Chandler", "Chang", "Chapman", "Charles", "Chase", "Chavez", "Chen", + "Cherry", "Christian", "Church", "Cisneros", "Clarke", "Clay", "Clayton", "Clements", "Clemons", "Cleveland", "Cline", "Cobb", + "Cochran", "Coffey", "Cohen", "Cole", "Combs", "Compton", "Conley", "Conner", "Conrad", "Contreras", "Conway", "Cooke", + "Copeland", "Cortez", "Cotton", "Cowan", "Craig", "Crane", "Crawford", "Crosby", "Cross", "Cruz", "Cummings", "Cunningham", + "Curry", "Curtis", "Dalton", "Daniel", "Daniels", "Daugherty", "Davenport", "David", "Davidson", "Day", "Dean", "Decker", + "Delacruz", "Dennis", "Deleon", "Delgado", "Dempsey", "Depp", "Devin", "Dickerson", "Dillard", "Dillon", "Dixon", "Dodson", + "Dominguez", "Donaldson", "Donovan", "Dorsey", "Dotson", "Douglas", "Downs", "Doyle", "Drake", "Dudley", "Duffy", "Duke", + "Dunlap", "Dunn", "Durham", "Dyer", "Eaton", "Elliott", "Ellis", "Ellison", "Emerson", "England", "English", "Erickson", + "Espinoza", "Estes", "Estrada", "Ewing", "Farley", "Farmer", "Farrell", "Faulkner", "Ferguson", "Fernandez", "Ferrell", + "Fields", "Finch", "Finley", "Fitzgerald", "Fleming", "Flores", "Flynn", "Foley", "Forbes", "Ford", "Foreman", "Fowler", "Fox", + "Francis", "Franco", "Frank", "Franklin", "Franks", "Frazier", "Frederick", "French", "Frost", "Fry", "Fuentes", "Fuller", + "Gaines", "Gallagher", "Galloway", "Gamble", "Garner", "Garrett", "Garrison", "Garza", "Gates", "Gay", "George", "Gibbs", + "Gibson", "Gilbert", "Giles", "Gill", "Gillespie", "Gilliam", "Glass", "Glenn", "Glover", "Golden", "Good", "Goodman", + "Goodwin", "Gordon", "Gould", "Grady", "Graham", "Grant", "Graves", "Greene", "Griffin", "Griffith", "Gross", "Guerra", + "Guerrero", "Guthrie", "Gutierrez", "Hahn", "Haley", "Hampton", "Hancock", "Haney", "Hansen", "Hardin", "Harding", "Hardy", + "Harmon", "Harper", "Harrington", "Hart", "Hartman", "Harvey", "Hatfield", "Hawkins", "Hayden", "Hayes", "Haynes", "Heath", + "Henderson", "Hendricks", "Henry", "Henson", "Herman", "Herrera", "Hess", "Hickman", "Hicks", "Higgins", "Hines", "Hinton", + "Hobbs", "Hodge", "Hodges", "Hoffman", "Holder", "Holland", "Holman", "Holmes", "Holt", "Hood", "Hooper", "Hoover", "Hopkins", + "Horne", "Horton", "House", "Houston", "Howell", "Hubbard", "Hudson", "Huff", "Huffman", "Hunt", "Hunter", "Hurley", "Hurst", + "Ingram", "Irwin", "Jacobs", "Jacoby", "Jarvis", "Jefferson", "Jennings", "Jimenez", "Johns", "Johnston", "Joyce", "Juarez", + "Justice", "Kane", "Kaufman", "Keith", "Keller", "Kelley", "Kemp", "Kennedy", "Kent", "Kerr", "Key", "Kidd", "Kinney", "Kirby", + "Kirk", "Klein", "Knapp", "Knight", "Knowles", "Lamb", "Lambert", "Lancaster", "Landry", "Lane", "Lang", "Langley", "Larsen", + "Lawrence", "Lawson", "Leach", "Leblanc", "Levine", "Levy", "Lindsey", "Little", "Livingston", "Lloyd", "Logan", "Long", "Love", + "Lowe", "Lucas", "Luna", "Lynch", "Macias", "Mack", "Maddox", "Maldonado", "Malone", "Mann", "Manning", "Marks", "Marquez", + "Marshall", "Mason", "Massey", "Mathews", "Mathis", "Mccall", "Mccarthy", "Mccormick", "Mcdaniel", "Mcdonald", "Meyer", "Miles", + "Montoya", "Montgomery", "Montes", "Moon", "Morales", "Moreno", "Moss", "Mueller", "Mullen", "Mullins", "Munoz", "Myers", + "Nash", "Navarro", "Neal", "Newton", "Nichols", "Noble", "Nolan", "Norris", "Norton", "Novak", "Ochoa", "Oconnor", "Odom", + "Oliver", "Olson", "Oneal", "Ortega", "Ortiz", "Osborne", "Owen", "Owens", "Pace", "Pacheco", "Padilla", "Page", "Palmer", + "Park", "Parsons", "Patel", "Patrick", "Paul", "Payne", "Pearson", "Peck", "Pena", "Pennington", "Perkins", "Phelps", "Pittman", + "Poole", "Pope", "Porter", "Potter", "Pratt", "Preston", "Pruitt", "Quinn", "Ramos", "Randall", "Rasmussen", "Ray", "Raymond", + "Reeves", "Reid", "Reyes", "Reynolds", "Rios", "Roach", "Robbins", "Rocha", "Roman", "Romero", "Rosa", "Rowe", "Roy", "Ruiz", + "Rush", "Rutledge", "Salazar", "Salinas", "Sanders", "Santana", "Santiago", "Santos", "Savage", "Schmidt", "Schneider", + "Schroeder", "Sharp", "Shaw", "Shelton", "Shepard", "Shepherd", "Sheppard", "Sherman", "Shields", "Short", "Silva", "Simmons", + "Simon", "Simpson", "Sims", "Skinner", "Slater", "Sloan", "Small", "Snow", "Snyder", "Solomon", "Sosa", "Sparks", "Spears", + "Spence", "Stafford", "Stanley", "Stark", "Steele", "Stephens", "Stephenson", "Stone", "Strickland", "Strong", "Suarez", + "Sullivan", "Summers", "Sutton", "Swanson", "Sweeney", "Sweet", "Tanner", "Tate", "Terrell", "Terry", "Todd", "Townsend", + "Travis", "Trujillo", "Underwood", "Valdez", "Valencia", "Vargas", "Vasquez", "Vaughn", "Vega", "Velasquez", "Villarreal", + "Vincent", "Vinson", "Wade", "Wagner", "Wall", "Walsh", "Walter", "Walters", "Walton", "Warner", "Warren", "Waters", "Weaver", + "Webb", "Weber", "Weeks", "Weiss", "Wells", "Wheeler", "Whitaker", "Whitfield", "Wilcox", "Wilkerson", "Wilkins", "Wilkinson", + "Willis", "Winters", "Wise", "Wolfe", "Wong", "Woodard", "Woods", "Wooten", "Workman", "Wyatt", "Yates", "York", "Zamora", + "Zimmerman"); private Utils() {} @@ -522,27 +591,27 @@ public static String randomDateTimeGenerator(String dateFormat) { } private static int getYear() { - return 1000 + r.nextInt(1001); + return 2000 + random.nextInt(31); } private static int getMonth() { - return 10 + r.nextInt(3); + return 10 + random.nextInt(3); } private static int getDay() { - return 10 + r.nextInt(16); + return 10 + random.nextInt(16); } private static int getHour() { - return 10 + r.nextInt(14); + return 10 + random.nextInt(14); } private static int getMinute() { - return 10 + r.nextInt(50); + return 10 + random.nextInt(50); } private static int getSecond() { - return 10 + r.nextInt(50); + return 10 + random.nextInt(50); } public static String arrayDateToString(List intArray) { @@ -595,10 +664,10 @@ public static Double getDoubleValue(BigDecimal amount) { } public static String randomFirstNameGenerator() { - return firstNames.get(r.nextInt(firstNames.size())); + return firstNames.get(random.nextInt(firstNames.size())); } public static String randomLastNameGenerator() { - return lastNames.get(r.nextInt(lastNames.size())); + return lastNames.get(random.nextInt(lastNames.size())); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java index 42e28206904..5937033e68e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/AccountHelper.java @@ -150,15 +150,15 @@ public HashMap getAccountingWithRunningBalanceById(final String accountId) { } public static PostGLAccountsResponse createGLAccount(final PostGLAccountsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.createGLAccount1(request)); + return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.createGLAccount(request)); } public static DeleteGLAccountsResponse deleteGLAccount(final Long requestId) { - return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.deleteGLAccount1(requestId)); + return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.deleteGLAccount(requestId)); } public static PutGLAccountsResponse updateGLAccount(final Long requestId, final PutGLAccountsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.updateGLAccount1(requestId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().glAccounts.updateGLAccount(requestId, request)); } public static GetGLAccountsResponse getGLAccount(final Long glAccountId) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java index cf438213d5f..53b6b5c75ab 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/FinancialActivityAccountHelper.java @@ -92,7 +92,8 @@ public Integer deleteFinancialActivityAccount(final Integer financialActivityAcc } public PostFinancialActivityAccountsResponse createFinancialActivityAccount(PostFinancialActivityAccountsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().financialActivyAccountMappings.createGLAccount(request)); + return Calls.ok(FineractClientHelper.getFineractClient().financialActivyAccountMappings + .createGLAccountMappingFinancialActivityAccount(request)); } public List getAllFinancialActivityAccounts() { @@ -100,6 +101,7 @@ public List getAllFinancialActivityAccount } public DeleteFinancialActivityAccountsResponse deleteFinancialActivityAccount(Long financialMappingId) { - return Calls.ok(FineractClientHelper.getFineractClient().financialActivyAccountMappings.deleteGLAccount(financialMappingId)); + return Calls.ok(FineractClientHelper.getFineractClient().financialActivyAccountMappings + .deleteGLAccountMappingFinancialActivityAccount(financialMappingId)); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java index 62744304c60..db98031e424 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/accounting/JournalEntryHelper.java @@ -194,7 +194,7 @@ public static PostJournalEntriesResponse createJournalEntry(String command, Jour } public static GetJournalEntriesTransactionIdResponse retrieveJournalEntryByTransactionId(final String transactionId) { - return Calls.ok(FineractClientHelper.getFineractClient().journalEntries.retrieveAll1(// + return Calls.ok(FineractClientHelper.getFineractClient().journalEntries.retrieveAllJournalEntries(// null, null, null, null, null, null, null, transactionId, null, // null, null, null, null, null, null, null, null, null, true)); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java index 9e3c740d7b3..22cbaec5af1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/commands/MakercheckersHelper.java @@ -26,6 +26,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.fineract.client.models.PostMakerCheckersResponse; +import org.apache.fineract.client.util.Calls; +import org.apache.fineract.client.util.FineractClient; import org.apache.fineract.client.util.JSON; import org.apache.fineract.integrationtests.common.Utils; @@ -80,4 +83,8 @@ public void approveMakerCheckerEntry(Long auditId) { String url = MAKERCHECKER_URL + "/" + auditId + "?command=approve&" + Utils.TENANT_IDENTIFIER; return Utils.performServerPost(requestSpec, responseSpec, url, "", ""); } + + public static PostMakerCheckersResponse rejectMakerCheckerEntry(FineractClient client, Long auditId) { + return Calls.ok(client.makerCheckers.approveMakerCheckerEntry(auditId, "reject")); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java index df77558967c..473c585bb67 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductHelper.java @@ -18,8 +18,10 @@ */ package org.apache.fineract.integrationtests.common.loans; +import java.util.Collection; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoanProductsTemplateResponse; +import org.apache.fineract.client.models.LoanProductBasicDetailsData; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; @@ -36,7 +38,7 @@ public PostLoanProductsResponse createLoanProduct(PostLoanProductsRequest reques } public GetLoanProductsProductIdResponse retrieveLoanProductByExternalId(String externalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.retrieveLoanProductDetails1(externalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.retrieveLoanProductDetailsByExternalId(externalId)); } public GetLoanProductsProductIdResponse retrieveLoanProductById(Long loanProductId) { @@ -44,7 +46,7 @@ public GetLoanProductsProductIdResponse retrieveLoanProductById(Long loanProduct } public PutLoanProductsProductIdResponse updateLoanProductByExternalId(String externalId, PutLoanProductsProductIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.updateLoanProduct1(externalId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.updateLoanProductByExternalId(externalId, request)); } public PutLoanProductsProductIdResponse updateLoanProductById(Long loanProductId, PutLoanProductsProductIdRequest request) { @@ -52,6 +54,10 @@ public PutLoanProductsProductIdResponse updateLoanProductById(Long loanProductId } public GetLoanProductsTemplateResponse getLoanProductTemplate(boolean isProductMixTemplate) { - return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.retrieveTemplate11(isProductMixTemplate)); + return Calls.ok(FineractClientHelper.getFineractClient().loanProducts.retrieveTemplateLoanProduct(isProductMixTemplate)); + } + + public static Collection fetchProductBasicDetailsList() { + return Calls.ok(FineractClientHelper.getFineractClient().loanProductsDetails.fetchProducts()); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 371c26649d8..b6c01087d66 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -300,7 +300,7 @@ public PutLoansLoanIdResponse modifyLoanCommand(final Integer loanId, final Stri public PutLoansLoanIdResponse modifyLoanApplication(final String loanExternalId, final String command, final PutLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.modifyLoanApplication1(loanExternalId, request, command)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.modifyLoanApplicationByExternalId(loanExternalId, request, command)); } // TODO: Rewrite to use fineract-client instead! @@ -416,7 +416,7 @@ public List getLoanDelinquencyActions(final Long } public List getLoanDelinquencyActions(String externalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.getLoanDelinquencyActions1(externalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.getLoanDelinquencyActionsByExternalId(externalId)); } public PostLoansDelinquencyActionResponse createLoanDelinquencyAction(final Long loanid, DelinquencyAction action, String startDate, @@ -430,8 +430,8 @@ public PostLoansDelinquencyActionResponse createLoanDelinquencyAction(String ext String endDate) { PostLoansDelinquencyActionRequest postLoansDelinquencyAction = new PostLoansDelinquencyActionRequest().action(action.name()) .startDate(startDate).endDate(endDate).locale("en").dateFormat("dd MMMM yyyy"); - return Calls - .ok(FineractClientHelper.getFineractClient().loans.createLoanDelinquencyAction1(externalId, postLoansDelinquencyAction)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.createLoanDelinquencyActionByExternalId(externalId, + postLoansDelinquencyAction)); } public PostLoansDelinquencyActionResponse createLoanDelinquencyAction(final Long loanid, DelinquencyAction action, String startDate) { @@ -472,15 +472,15 @@ public List getLoanCharges(final Long loa } public List getLoanCharges(final String loanExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveAllLoanCharges1(loanExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveAllLoanChargesByLoanExternalId(loanExternalId)); } public GetLoansLoanIdChargesTemplateResponse getLoanChargeTemplate(final Long loanId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveTemplate8(loanId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveTemplateLoanCharge(loanId)); } public GetLoansLoanIdChargesTemplateResponse getLoanChargeTemplate(final String loanExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveTemplate9(loanExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveTemplateLoanChargeByLoanExternalId(loanExternalId)); } // TODO: Rewrite to use fineract-client instead! @@ -950,20 +950,20 @@ public PostLoansLoanIdTransactionsResponse capitalizedIncomeAdjustment(final Lon public PostLoansLoanIdTransactionsResponse capitalizedIncomeAdjustment(final String loanExternalId, final Long transactionId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, - request, "capitalizedIncomeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanExternalId(loanExternalId, + transactionId, request, "capitalizedIncomeAdjustment")); } public PostLoansLoanIdTransactionsResponse capitalizedIncomeAdjustment(final String loanExternalId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, - transactionExternalId, request, "capitalizedIncomeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanAndTransactionExternalId( + loanExternalId, transactionExternalId, request, "capitalizedIncomeAdjustment")); } public PostLoansLoanIdTransactionsResponse capitalizedIncomeAdjustment(final Long loanId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, - request, "capitalizedIncomeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByTransactionExternalId(loanId, + transactionExternalId, request, "capitalizedIncomeAdjustment")); } public PostLoansLoanIdTransactionsResponse capitalizedIncomeAdjustment(final Long loanId, final Long capitalizedIncomeTransactionId, @@ -980,20 +980,20 @@ public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanI public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final Long transactionId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, - request, "buyDownFeeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanExternalId(loanExternalId, + transactionId, request, "buyDownFeeAdjustment")); } public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, - transactionExternalId, request, "buyDownFeeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanAndTransactionExternalId( + loanExternalId, transactionExternalId, request, "buyDownFeeAdjustment")); } public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, - request, "buyDownFeeAdjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByTransactionExternalId(loanId, + transactionExternalId, request, "buyDownFeeAdjustment")); } public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId, @@ -1080,8 +1080,8 @@ public PostLoansLoanIdTransactionsResponse makeInterestPaymentWaiver(final Long public PostLoansLoanIdTransactionsResponse makeInterestPaymentWaiver(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "interestPaymentWaiver")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "interestPaymentWaiver")); } public PostLoansLoanIdTransactionsResponse reAge(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1110,49 +1110,52 @@ public PutChargeTransactionChangesResponse undoWaiveLoanCharge(final Long loanId public PutChargeTransactionChangesResponse undoWaiveLoanCharge(final Long loanId, final String transactionExternalId, final PutChargeTransactionChangesRequest request) { log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------"); - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.undoWaiveCharge1(loanId, transactionExternalId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.undoWaiveChargeByTransactionExternalId(loanId, + transactionExternalId, request)); } public PutChargeTransactionChangesResponse undoWaiveLoanCharge(final String loanExternalId, final Long transactionId, final PutChargeTransactionChangesRequest request) { log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------"); - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.undoWaiveCharge2(loanExternalId, transactionId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.undoWaiveChargeByLoanExternalId(loanExternalId, + transactionId, request)); } public PutChargeTransactionChangesResponse undoWaiveLoanCharge(final String loanExternalId, final String transactionExternalId, final PutChargeTransactionChangesRequest request) { log.info("--------------------------------- UNDO WAIVE CHARGES FOR LOAN --------------------------------"); - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.undoWaiveCharge3(loanExternalId, transactionExternalId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions + .undoWaiveChargeByLoanAndTransactionExternalId(loanExternalId, transactionExternalId, request)); } public PostLoansLoanIdChargesChargeIdResponse waiveLoanCharge(final Long loanId, final Long loanChargeId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge2(loanId, loanChargeId, request, "waive")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeOnExistingCharge(loanId, loanChargeId, + request, "waive")); } public PostLoansLoanIdChargesChargeIdResponse waiveLoanCharge(final String loanExternalId, final Long loanChargeId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge4(loanExternalId, loanChargeId, request, "waive")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges + .executeLoanChargeByLoanExternalIdOnExistingCharge(loanExternalId, loanChargeId, request, "waive")); } public PostLoansLoanIdChargesChargeIdResponse waiveLoanCharge(final Long loanId, final String loanChargeExternalId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge3(loanId, loanChargeExternalId, request, "waive")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeByChargeExternalId(loanId, + loanChargeExternalId, request, "waive")); } public PostLoansLoanIdChargesChargeIdResponse waiveLoanCharge(final String loanExternalId, final String loanChargeExternalId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge5(loanExternalId, loanChargeExternalId, - request, "waive")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId, request, "waive")); } public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "repayment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "repayment")); } public PostLoansLoanIdTransactionsResponse makeMerchantIssuedRefund(final Long loanId, @@ -1163,8 +1166,8 @@ public PostLoansLoanIdTransactionsResponse makeMerchantIssuedRefund(final Long l public PostLoansLoanIdTransactionsResponse makeMerchantIssuedRefund(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "merchantIssuedRefund")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "merchantIssuedRefund")); } public PostLoansLoanIdTransactionsResponse makePayoutRefund(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1173,8 +1176,8 @@ public PostLoansLoanIdTransactionsResponse makePayoutRefund(final Long loanId, f public PostLoansLoanIdTransactionsResponse makePayoutRefund(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "payoutRefund")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "payoutRefund")); } public PostLoansLoanIdTransactionsResponse makeChargeRefund(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1183,8 +1186,8 @@ public PostLoansLoanIdTransactionsResponse makeChargeRefund(final Long loanId, f public PostLoansLoanIdTransactionsResponse makeChargeRefund(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "chargeRefund")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "chargeRefund")); } public PostLoansLoanIdTransactionsResponse manualInterestRefund(final Long loanId, final Long targetTransactionId, @@ -1200,8 +1203,8 @@ public PostLoansLoanIdTransactionsResponse makeGoodwillCredit(final Long loanId, public PostLoansLoanIdTransactionsResponse makeGoodwillCredit(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "goodwillCredit")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "goodwillCredit")); } public PostLoansLoanIdTransactionsResponse makeWaiveInterest(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1210,8 +1213,8 @@ public PostLoansLoanIdTransactionsResponse makeWaiveInterest(final Long loanId, public PostLoansLoanIdTransactionsResponse makeWaiveInterest(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "waiveinterest")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "waiveinterest")); } public PostLoansLoanIdTransactionsResponse makeWriteoff(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1219,8 +1222,8 @@ public PostLoansLoanIdTransactionsResponse makeWriteoff(final Long loanId, final } public PostLoansLoanIdTransactionsResponse makeWriteoff(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls - .ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "writeoff")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "writeoff")); } public PostLoansLoanIdTransactionsResponse makeUndoWriteoff(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1229,8 +1232,8 @@ public PostLoansLoanIdTransactionsResponse makeUndoWriteoff(final Long loanId, f public PostLoansLoanIdTransactionsResponse makeUndoWriteoff(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "undowriteoff")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "undowriteoff")); } public PostLoansLoanIdTransactionsResponse makeRecoveryPayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1240,8 +1243,8 @@ public PostLoansLoanIdTransactionsResponse makeRecoveryPayment(final Long loanId public PostLoansLoanIdTransactionsResponse makeRecoveryPayment(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "recoverypayment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "recoverypayment")); } public PostLoansLoanIdTransactionsResponse makeRefundByCash(final Long loanId, final PostLoansLoanIdTransactionsRequest request) { @@ -1250,8 +1253,8 @@ public PostLoansLoanIdTransactionsResponse makeRefundByCash(final Long loanId, f public PostLoansLoanIdTransactionsResponse makeRefundByCash(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "refundByCash")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "refundByCash")); } public PostLoansLoanIdTransactionsResponse makeCreditBalanceRefund(final Long loanId, @@ -1262,26 +1265,26 @@ public PostLoansLoanIdTransactionsResponse makeCreditBalanceRefund(final Long lo public PostLoansLoanIdTransactionsResponse makeCreditBalanceRefund(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "creditBalanceRefund")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "creditBalanceRefund")); } public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final String loanExternalId, final Long transactionId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, - request, "undo")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanExternalId(loanExternalId, + transactionId, request, "undo")); } public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final String loanExternalId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, - transactionExternalId, request, "undo")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions + .adjustLoanTransactionByLoanAndTransactionExternalId(loanExternalId, transactionExternalId, request, "undo")); } public PostLoansLoanIdTransactionsResponse reverseLoanTransaction(final Long loanId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, - request, "undo")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByTransactionExternalId(loanId, + transactionExternalId, request, "undo")); } public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final Long loanId, final Long transactionId, @@ -1292,38 +1295,38 @@ public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final Long public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final String loanExternalId, final Long transactionId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, - request, "chargeback")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanExternalId(loanExternalId, + transactionId, request, "chargeback")); } public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final String loanExternalId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, - transactionExternalId, request, "chargeback")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions + .adjustLoanTransactionByLoanAndTransactionExternalId(loanExternalId, transactionExternalId, request, "chargeback")); } public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final Long loanId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, - request, "chargeback")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByTransactionExternalId(loanId, + transactionExternalId, request, "chargeback")); } public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final String loanExternalId, final Long transactionId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, - request, "adjust")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByLoanExternalId(loanExternalId, + transactionId, request, "adjust")); } public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final String loanExternalId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, - transactionExternalId, request, "adjust")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions + .adjustLoanTransactionByLoanAndTransactionExternalId(loanExternalId, transactionExternalId, request, "adjust")); } public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Long loanId, final String transactionExternalId, final PostLoansLoanIdTransactionsTransactionIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, - request, "adjust")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransactionByTransactionExternalId(loanId, + transactionExternalId, request, "adjust")); } public PostLoansLoanIdTransactionsResponse adjustLoanTransaction(final Long loanId, final Long transactionId, @@ -1393,7 +1396,8 @@ public PostLoansLoanIdChargesResponse addLoanCharge(final Long loanId, final Pos } public PostLoansLoanIdChargesResponse addLoanCharge(final String loanExternalId, final PostLoansLoanIdChargesRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge1(loanExternalId, request, "")); + return Calls + .ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeByLoanExternalId(loanExternalId, request, "")); } // TODO: Rewrite to use fineract-client instead! @@ -1473,18 +1477,20 @@ public PutLoansLoanIdChargesChargeIdResponse updateLoanCharge(final Long loanId, public PutLoansLoanIdChargesChargeIdResponse updateLoanCharge(final Long loanId, final String loanChargeExternalId, final PutLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanCharge1(loanId, loanChargeExternalId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanChargeByChargeExternalId(loanId, + loanChargeExternalId, request)); } public PutLoansLoanIdChargesChargeIdResponse updateLoanCharge(final String loanExternalId, final Long loanChargeId, final PutLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanCharge2(loanExternalId, loanChargeId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanChargeByLoanExternalId(loanExternalId, loanChargeId, + request)); } public PutLoansLoanIdChargesChargeIdResponse updateLoanCharge(final String loanExternalId, final String loanChargeExternalId, final PutLoansLoanIdChargesChargeIdRequest request) { - return Calls - .ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanCharge3(loanExternalId, loanChargeExternalId, request)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.updateLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId, request)); } public DeleteLoansLoanIdChargesChargeIdResponse deleteLoanCharge(final Long loanId, final Long loanChargeId) { @@ -1492,15 +1498,18 @@ public DeleteLoansLoanIdChargesChargeIdResponse deleteLoanCharge(final Long loan } public DeleteLoansLoanIdChargesChargeIdResponse deleteLoanCharge(final Long loanId, final String loanChargeExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanCharge1(loanId, loanChargeExternalId)); + return Calls + .ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanChargeByChargeExternalId(loanId, loanChargeExternalId)); } public DeleteLoansLoanIdChargesChargeIdResponse deleteLoanCharge(final String loanExternalId, final Long loanChargeId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanCharge2(loanExternalId, loanChargeId)); + return Calls + .ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanChargeByLoanExternalId(loanExternalId, loanChargeId)); } public DeleteLoansLoanIdChargesChargeIdResponse deleteLoanCharge(final String loanExternalId, final String loanChargeExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanCharge3(loanExternalId, loanChargeExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.deleteLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId)); } // TODO: Rewrite to use fineract-client instead! @@ -1553,13 +1562,14 @@ public HashMap undoWaiveChargesForLoan(final Integer loanId, final Integer trans public PostLoansLoanIdChargesChargeIdResponse chargeAdjustment(final Long loanId, final Long chargeId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge2(loanId, chargeId, request, "adjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeOnExistingCharge(loanId, chargeId, request, + "adjustment")); } public PostLoansLoanIdChargesChargeIdResponse chargeAdjustment(final String loanExternalId, final String loanChargeExternalId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge5(loanExternalId, loanChargeExternalId, - request, "adjustment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId, request, "adjustment")); } // TODO: Rewrite to use fineract-client instead! @@ -1588,19 +1598,20 @@ public Integer payChargesForLoan(final Integer loanId, final Integer loanchargeI public PostLoansLoanIdChargesChargeIdResponse payLoanCharge(final Long loanId, final Long loanChargeId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge2(loanId, loanChargeId, request, "pay")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeOnExistingCharge(loanId, loanChargeId, + request, "pay")); } public PostLoansLoanIdChargesChargeIdResponse payLoanCharge(final String loanExternalId, final Long loanChargeId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls - .ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge4(loanExternalId, loanChargeId, request, "pay")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges + .executeLoanChargeByLoanExternalIdOnExistingCharge(loanExternalId, loanChargeId, request, "pay")); } public PostLoansLoanIdChargesChargeIdResponse payLoanCharge(final String loanExternalId, final String loanChargeExternalId, final PostLoansLoanIdChargesChargeIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanCharge5(loanExternalId, loanChargeExternalId, - request, "pay")); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.executeLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId, request, "pay")); } // TODO: Rewrite to use fineract-client instead! @@ -1629,15 +1640,18 @@ public GetLoansLoanIdChargesChargeIdResponse getLoanCharge(final Long loanId, fi } public GetLoansLoanIdChargesChargeIdResponse getLoanCharge(final String loanExternalId, final Long loanChargeId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveLoanCharge2(loanExternalId, loanChargeId)); + return Calls + .ok(FineractClientHelper.getFineractClient().loanCharges.retrieveLoanChargeByLoanExternalId(loanExternalId, loanChargeId)); } public GetLoansLoanIdChargesChargeIdResponse getLoanCharge(final Long loanId, final String loanChargeExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveLoanCharge1(loanId, loanChargeExternalId)); + return Calls.ok( + FineractClientHelper.getFineractClient().loanCharges.retrieveLoanChargeByChargeExternalId(loanId, loanChargeExternalId)); } public GetLoansLoanIdChargesChargeIdResponse getLoanCharge(final String loanExternalId, final String loanChargeExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveLoanCharge3(loanExternalId, loanChargeExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loanCharges.retrieveLoanChargeByLoanAndChargeExternalId(loanExternalId, + loanChargeExternalId)); } // TODO: Rewrite to use fineract-client instead! @@ -1677,7 +1691,7 @@ public GetLoansLoanIdResponse getLoanDetails(final Long loanId) { } public GetLoansLoanIdResponse getLoanDetails(final String loanExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveLoan1(loanExternalId, false, "all", null, null)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveLoanByExternalId(loanExternalId, false, "all", null, null)); } public GetLoansLoanIdTransactionsResponse getLoanTransactions(final Long loanId) { @@ -1730,8 +1744,8 @@ public PostLoansLoanIdTransactionsResponse createManualInterestRefund(Long loanI // org.apache.fineract.client.models.PostLoansLoanIdRequest) public GetLoansResponse retrieveAllLoans(final String accountNumber, final String associations, final Long clientId) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveAll27(null, 0, 10, null, null, accountNumber, associations, - clientId, null)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveAllLoans(null, 0, 10, null, null, accountNumber, + associations, clientId, null)); } @Deprecated(forRemoval = true) @@ -2916,20 +2930,20 @@ public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(Lo public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command, String dateFormat, String transactionDate, String locale) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate1(loanExternalIdStr, command, - dateFormat, transactionDate, locale, null)); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions + .retrieveTransactionTemplateByLoanExternalId(loanExternalIdStr, command, dateFormat, transactionDate, locale, null)); } public GetLoansApprovalTemplateResponse getLoanApprovalTemplate(String loanExternalIdStr) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveApprovalTemplate1(loanExternalIdStr, "approval")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.retrieveApprovalTemplateByExternalId(loanExternalIdStr, "approval")); } public DeleteLoansLoanIdResponse deleteLoanApplication(String loanExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.deleteLoanApplication1(loanExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.deleteLoanApplicationByExternalId(loanExternalId)); } public List getLoanDelinquencyTags(String loanExternalId) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.getDelinquencyTagHistory1(loanExternalId)); + return Calls.ok(FineractClientHelper.getFineractClient().loans.getDelinquencyTagHistoryByExternalId(loanExternalId)); } public PostLoansResponse applyLoan(PostLoansRequest request) { @@ -2943,7 +2957,7 @@ public void applyLoanWithError(PostLoansRequest request, Integer httpStatus) { } public PostLoansLoanIdResponse approveLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "approve")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "approve")); } public PostLoansLoanIdResponse approveLoan(Long loanId, PostLoansLoanIdRequest request) { @@ -2951,7 +2965,7 @@ public PostLoansLoanIdResponse approveLoan(Long loanId, PostLoansLoanIdRequest r } public PostLoansLoanIdResponse rejectLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "reject")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "reject")); } public PostLoansLoanIdResponse rejectLoan(Long loanId, PostLoansLoanIdRequest request) { @@ -2959,11 +2973,12 @@ public PostLoansLoanIdResponse rejectLoan(Long loanId, PostLoansLoanIdRequest re } public PostLoansLoanIdResponse withdrawnByApplicantLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "withdrawnByApplicant")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, + "withdrawnByApplicant")); } public PostLoansLoanIdResponse disburseLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "disburse")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "disburse")); } public PostLoansLoanIdResponse disburseLoan(Long loanId, PostLoansLoanIdRequest request) { @@ -2991,15 +3006,18 @@ public PostLoansLoanIdResponse disburseLoan(Long loanId, String date, Double amo } public PostLoansLoanIdResponse disburseToSavingsLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "disburseToSavings")); + return Calls.ok( + FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "disburseToSavings")); } public PostLoansLoanIdResponse undoApprovalLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "undoapproval")); + return Calls + .ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "undoapproval")); } public PostLoansLoanIdResponse undoDisbursalLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "undodisbursal")); + return Calls + .ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "undodisbursal")); } public PostLoansLoanIdResponse undoDisbursalLoan(Long loanId, PostLoansLoanIdRequest request) { @@ -3007,7 +3025,8 @@ public PostLoansLoanIdResponse undoDisbursalLoan(Long loanId, PostLoansLoanIdReq } public PostLoansLoanIdResponse undoLastDisbursalLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "undolastdisbursal")); + return Calls.ok( + FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "undolastdisbursal")); } public PostLoansLoanIdResponse undoLastDisbursalLoan(Long loanId, PostLoansLoanIdRequest request) { @@ -3015,24 +3034,28 @@ public PostLoansLoanIdResponse undoLastDisbursalLoan(Long loanId, PostLoansLoanI } public PostLoansLoanIdResponse assignLoanOfficerLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "assignloanofficer")); + return Calls.ok( + FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "assignloanofficer")); } public PostLoansLoanIdResponse unassignLoanOfficerLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "unassignloanofficer")); + return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, + "unassignloanofficer")); } public PostLoansLoanIdResponse recoverGuaranteesLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "recoverGuarantees")); + return Calls.ok( + FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "recoverGuarantees")); } public PostLoansLoanIdResponse assignDelinquencyLoan(String loanExternalId, PostLoansLoanIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "assigndelinquency")); + return Calls.ok( + FineractClientHelper.getFineractClient().loans.stateTransitionsByExternalId(loanExternalId, request, "assigndelinquency")); } public PostLoansLoanIdTransactionsResponse closeRescheduledLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "close-rescheduled")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "close-rescheduled")); } public PostLoansLoanIdTransactionsResponse closeRescheduledLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3041,8 +3064,8 @@ public PostLoansLoanIdTransactionsResponse closeRescheduledLoan(Long loanId, Pos } public PostLoansLoanIdTransactionsResponse closeLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls - .ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "close")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "close")); } public PostLoansLoanIdTransactionsResponse closeLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3050,8 +3073,8 @@ public PostLoansLoanIdTransactionsResponse closeLoan(Long loanId, PostLoansLoanI } public PostLoansLoanIdTransactionsResponse forecloseLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "foreclosure")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "foreclosure")); } public PostLoansLoanIdTransactionsResponse forecloseLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3059,8 +3082,8 @@ public PostLoansLoanIdTransactionsResponse forecloseLoan(Long loanId, PostLoansL } public PostLoansLoanIdTransactionsResponse chargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "charge-off")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "charge-off")); } public PostLoansLoanIdTransactionsResponse chargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3068,8 +3091,8 @@ public PostLoansLoanIdTransactionsResponse chargeOffLoan(Long loanId, PostLoansL } public PostLoansLoanIdTransactionsResponse undoChargeOffLoan(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, - "undo-charge-off")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "undo-charge-off")); } public PostLoansLoanIdTransactionsResponse undoChargeOffLoan(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3111,8 +3134,8 @@ public PostLoanProductsResponse createLoanProduct(PostLoanProductsRequest reques } public PostLoansLoanIdTransactionsResponse makeLoanDownPayment(String loanExternalId, PostLoansLoanIdTransactionsRequest request) { - return Calls.ok( - FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "downPayment")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "downPayment")); } public PostLoansLoanIdTransactionsResponse makeLoanDownPayment(Long loanId, PostLoansLoanIdTransactionsRequest request) { @@ -3144,8 +3167,8 @@ public Object disburseLoanWithTransactionAmountWithError(final String date, fina public PostLoansLoanIdTransactionsResponse writeOffLoanAccount(final String loanExternalId, final PostLoansLoanIdTransactionsRequest request) { - return Calls - .ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction1(loanExternalId, request, "writeoff")); + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransactionByLoanExternalId(loanExternalId, + request, "writeoff")); } // TODO: Rewrite to use fineract-client instead! diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java index 6e16ae923d6..e0c40c6fadc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java @@ -65,6 +65,21 @@ public Integer createCampaign(String reportName, Integer triggerType) { "resourceId"); } + public Integer createCampaignWithName(String reportName, Integer triggerType, String campaignName) { + log.info("---------------------------------CREATING A CAMPAIGN WITH NAME---------------------------------------------"); + final String CREATE_SMS_CAMPAIGNS_URL = SMS_CAMPAIGNS_URL + "?" + Utils.TENANT_IDENTIFIER; + return Utils.performServerPost(requestSpec, responseSpec, CREATE_SMS_CAMPAIGNS_URL, + getCreateCampaignJSONWithName(reportName, triggerType, campaignName), "resourceId"); + } + + public List createCampaignWithNameExpectingError(ResponseSpecification errorResponseSpec, String reportName, + Integer triggerType, String campaignName) { + log.info("---------------------------------CREATING A CAMPAIGN WITH NAME (EXPECTING ERROR)---------------------"); + final String CREATE_SMS_CAMPAIGNS_URL = SMS_CAMPAIGNS_URL + "?" + Utils.TENANT_IDENTIFIER; + return Utils.performServerPost(requestSpec, errorResponseSpec, CREATE_SMS_CAMPAIGNS_URL, + getCreateCampaignJSONWithName(reportName, triggerType, campaignName), "errors"); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) @@ -132,6 +147,10 @@ public Object performActionsOnCampaignWithFailure(final Integer generatedCampaig // org.apache.fineract.client.models.PostLoansLoanIdRequest) @Deprecated(forRemoval = true) public String getCreateCampaignJSON(String reportName, Integer triggerType) { + return getCreateCampaignJSONWithName(reportName, triggerType, Utils.randomStringGenerator("Campaign_Name_", 5)); + } + + public String getCreateCampaignJSONWithName(String reportName, Integer triggerType, String campaignName) { final HashMap map = new HashMap<>(); final HashMap paramValueMap = new HashMap<>(); Long reportId = getSelectedReportId(reportName); @@ -143,7 +162,7 @@ public String getCreateCampaignJSON(String reportName, Integer triggerType) { map.put("frequency", 1); map.put("interval", "1"); } - map.put("campaignName", Utils.randomStringGenerator("Campaign_Name_", 5)); + map.put("campaignName", campaignName); map.put("campaignType", 1); map.put("message", "Hi, this is from integtration tests runner"); map.put("locale", "en"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index 1fe7e77b82d..515c4c9f527 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -84,6 +84,7 @@ public class SavingsAccountHelper { private static final String DEPOSIT_SAVINGS_COMMAND = "deposit"; private static final String WITHDRAW_SAVINGS_COMMAND = "withdrawal"; + private static final String FORCE_WITHDRAW_SAVINGS_COMMAND = "force-withdrawal"; private static final String GSIM_SAVINGS = "/gsim"; private static final String GSIM_SAVINGS_COMMAND = "/gsimcommands"; private static final String GSIM_DEPOSIT_SAVINGS_COMMAND = "gsimDeposit"; @@ -378,7 +379,7 @@ public HashMap closeSavingsAccount(final Integer savingsID, String withdrawBalan } public PostSavingsAccountsAccountIdResponse closeSavingsAccount(final Long savingsId, PostSavingsAccountsAccountIdRequest request) { - return Calls.ok(FineractClientHelper.getFineractClient().savingsAccounts.handleCommands6(savingsId, request, "close")); + return Calls.ok(FineractClientHelper.getFineractClient().savingsAccounts.handleCommandsSavingsAccount(savingsId, request, "close")); } // TODO: Rewrite to use fineract-client instead! @@ -431,12 +432,20 @@ public Object withdrawalFromSavingsAccount(final Integer savingsId, final String public Response withdrawalFromSavingsAccount(final Long savingsId, PostSavingsAccountTransactionsRequest request) { - return Calls.executeU(FineractClientHelper.getFineractClient().savingsTransactions.transaction2(savingsId, request, "withdrawal")); + return Calls.executeU(FineractClientHelper.getFineractClient().savingsTransactions.createSavingsAccountTransaction(savingsId, + request, "withdrawal")); + } + + public Response forceWithdrawalFromSavingsAccount(final Long savingsId, + PostSavingsAccountTransactionsRequest request) { + return Calls.executeU(FineractClientHelper.getFineractClient().savingsTransactions.createSavingsAccountTransaction(savingsId, + request, "force-withdrawal")); } public Response depositIntoSavingsAccount(final Long savingsId, PostSavingsAccountTransactionsRequest request) { - return Calls.executeU(FineractClientHelper.getFineractClient().savingsTransactions.transaction2(savingsId, request, "deposit")); + return Calls.executeU(FineractClientHelper.getFineractClient().savingsTransactions.createSavingsAccountTransaction(savingsId, + request, "deposit")); } // TODO: Rewrite to use fineract-client instead! @@ -1068,8 +1077,8 @@ public SavingsAccountTransactionsSearchResponse searchSavingsTransactions(Intege } public Map querySavingsTransactions(Integer savingsId, PagedLocalRequestAdvancedQueryRequest request) { - String response = Calls - .ok(FineractClientHelper.getFineractClient().savingsTransactions.advancedQuery1(savingsId.longValue(), request)); + String response = Calls.ok(FineractClientHelper.getFineractClient().savingsTransactions + .advancedQuerySavingsAccountTransactions(savingsId.longValue(), request)); return JsonPath.from(response).get(""); } @@ -1524,4 +1533,11 @@ public BigDecimal getTotalAccrualAmount(Integer savingsId) { return total.setScale(2, java.math.RoundingMode.HALF_UP); } + public Object forceWithdrawalFromSavingsAccount(final Integer savingsId, final String amount, String date, + String jsonAttributeToGetback) { + LOG.info("\n--------------------------------- SAVINGS TRANSACTION FORCE WITHDRAWAL --------------------------------"); + return performSavingActions(createSavingsTransactionURL("force-withdrawal", savingsId), getSavingsTransactionJSON(amount, date), + jsonAttributeToGetback); + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsTestLifecycleExtension.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsTestLifecycleExtension.java index 11bd89e288f..f24e62bb5af 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsTestLifecycleExtension.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsTestLifecycleExtension.java @@ -64,8 +64,8 @@ public void afterAll(ExtensionContext context) { savingsIds.forEach(savingsId -> { try { this.savingsAccountHelper.postInterestForSavings(savingsId.intValue()); - SavingsAccountData savingsAccountData = Calls - .ok(FineractClientHelper.getFineractClient().savingsAccounts.retrieveOne26(savingsId, false, null, "all")); + SavingsAccountData savingsAccountData = Calls.ok( + FineractClientHelper.getFineractClient().savingsAccounts.retrieveSavingsAccount(savingsId, false, null, "all")); BigDecimal accountBalance = MathUtil.subtract(savingsAccountData.getSummary().getAvailableBalance(), savingsAccountData.getMinRequiredBalance(), MathContext.DECIMAL64); if (accountBalance.compareTo(BigDecimal.ZERO) > 0) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java index 7826136e36e..fbc2331c3f7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/shares/ShareAccountIntegrationTests.java @@ -983,6 +983,285 @@ public void testCreateShareAccountWithCharges() { Assertions.assertEquals("0", String.valueOf(summaryMap.get("totalPendingForApprovalShares"))); } + // Refactored Test 1 + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesAfterRejectedTransaction() { + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 15 April 2016 + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + + // Retrieve transactions and find the additional shares request + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + // Find and reject the additional shares request (15 April 2016) + String additionalSharesRequestId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(additionalSharesRequestId, "Additional shares request for 15 April 2016 should exist"); + + // Reject the additional shares request + rejectAdditionalSharesRequest(shareAccountId, additionalSharesRequestId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "15 April 2016", "purchasedSharesStatusType.rejected"); + + // Now try to apply additional shares with a date BEFORE the rejected transaction (10 April 2016) + // This should succeed because rejected transactions should be ignored in chronological validation + applyAdditionalShares(shareAccountId, "10 April 2016", "15"); + + // Verify the new transaction was successfully added + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionWithShares(transactions, "purchasedSharesType.purchased", "10 April 2016", "15", + "purchasedSharesStatusType.applied"); + } + + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAccountClosureBeforeRejectedTransaction() { + // FINERACT-2457: Account closure validation should ignore rejected/reversed transactions + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 20 May 2016 + applyAdditionalShares(shareAccountId, "20 May 2016", "30"); + + // Retrieve transactions and find the additional shares request + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + // Find and reject the additional shares request (20 May 2016) + String additionalSharesRequestId = findTransactionId(transactions, "purchasedSharesType.purchased", "20 May 2016"); + Assertions.assertNotNull(additionalSharesRequestId, "Additional shares request for 20 May 2016 should exist"); + + // Reject the additional shares request + rejectAdditionalSharesRequest(shareAccountId, additionalSharesRequestId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "20 May 2016", "purchasedSharesStatusType.rejected"); + + // Now try to close the account with a date BEFORE the rejected transaction (15 May 2016) + // This should succeed because rejected transactions should be ignored in chronological validation + Map closeAccountMap = new HashMap<>(); + closeAccountMap.put("note", "Share Account Close Note"); + closeAccountMap.put("dateFormat", "dd MMMM yyyy"); + closeAccountMap.put("closedDate", "15 May 2016"); + closeAccountMap.put("locale", "en"); + String closeJson = new Gson().toJson(closeAccountMap); + ShareAccountTransactionHelper.postCommand("close", shareAccountId, closeJson, requestSpec, responseSpec); + + // Verify the account was successfully closed + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.closed", String.valueOf(statusMap.get("code"))); + + Map timelineMap = (Map) shareAccountData.get("timeline"); + List closedDateList = (List) timelineMap.get("closedDate"); + LocalDate closedDate = LocalDate.of(closedDateList.get(0), closedDateList.get(1), closedDateList.get(2)); + Assertions.assertEquals("15 May 2016", closedDate.format(Utils.dateFormatter)); + } + + // Additional Test 1: Verify original validation still works (Negative Test) + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesBeforeActiveTransactionShouldFail() { + // FINERACT-2457: Verify that the fix didn't break the original chronological validation + // Transactions BEFORE active/approved transactions should still be REJECTED + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 15 April 2016 (this remains active/approved) + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + + // Verify the transaction exists and is in applied/active state + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + Assertions.assertNotNull(transactions); + + String transactionId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(transactionId, "Transaction for 15 April 2016 should exist"); + + // Try to apply additional shares BEFORE the active transaction (10 April 2016) + // This should FAIL because the April 15 transaction is ACTIVE/APPROVED + Map additionalSharesRequestMap = new HashMap<>(); + additionalSharesRequestMap.put("requestedDate", "10 April 2016"); + additionalSharesRequestMap.put("dateFormat", "dd MMMM yyyy"); + additionalSharesRequestMap.put("locale", "en"); + additionalSharesRequestMap.put("requestedShares", "15"); + String additionalSharesRequestJson = new Gson().toJson(additionalSharesRequestMap); + + ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + + try { + ShareAccountTransactionHelper.postCommand("applyadditionalshares", shareAccountId, additionalSharesRequestJson, requestSpec, + errorResponse); + + } catch (Exception e) { + Assertions.assertTrue( + e.getMessage().contains("chronological") || e.getMessage().contains("date") || e.getMessage().contains("before"), + "Error message should indicate chronological validation failure"); + } + } + + // Additional Test 3: Test edge case - transaction on same date as rejected transaction + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAdditionalSharesOnSameDateAsRejectedTransaction() { + // FINERACT-2457: Test behavior when applying shares on the SAME date as a rejected transaction + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply and reject shares on 15 April 2016 + applyAdditionalShares(shareAccountId, "15 April 2016", "20"); + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + String txId = findTransactionId(transactions, "purchasedSharesType.purchased", "15 April 2016"); + Assertions.assertNotNull(txId); + rejectAdditionalSharesRequest(shareAccountId, txId); + + // Verify transaction is rejected + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + verifyTransactionStatus(transactions, "purchasedSharesType.purchased", "15 April 2016", "purchasedSharesStatusType.rejected"); + + // Try to apply shares on the SAME date as the rejected transaction + // This should succeed since rejected transactions are ignored + applyAdditionalShares(shareAccountId, "15 April 2016", "15"); + + // Verify the new transaction was successfully added + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + transactions = (List>) shareAccountData.get("purchasedShares"); + + // Count how many transactions exist for 15 April 2016 + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + int countForDate = 0; + int appliedCountForDate = 0; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals("purchasedSharesType.purchased") && transactionDate.equals("15 April 2016")) { + countForDate++; + Map transactionStatusMap = (Map) transaction.get("status"); + String status = String.valueOf(transactionStatusMap.get("code")); + if (status.equals("purchasedSharesStatusType.applied")) { + appliedCountForDate++; + Assertions.assertEquals("15", String.valueOf(transaction.get("numberOfShares"))); + } + } + } + + // Should have 2 transactions for this date: 1 rejected, 1 applied + Assertions.assertEquals(2, countForDate, "Should have 2 transactions for 15 April 2016"); + Assertions.assertEquals(1, appliedCountForDate, "Should have 1 applied transaction for 15 April 2016"); + } + + // Additional Test 4: Test account closure before active transaction should still fail + @Test + @SuppressWarnings("unchecked") + public void testChronologicalAccountClosureBeforeActiveTransactionShouldFail() { + // FINERACT-2457: Verify that closing account before active transactions is still blocked + shareProductHelper = new ShareProductHelper(); + final Integer productId = createShareProduct(); + Assertions.assertNotNull(productId); + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + Integer savingsAccountId = SavingsAccountHelper.openSavingsAccount(requestSpec, responseSpec, clientId, "1000"); + Assertions.assertNotNull(savingsAccountId); + + // Setup and activate share account with initial shares on 01 March 2016 + final Integer shareAccountId = setupAndActivateShareAccount(clientId, productId, savingsAccountId, "01 March 2016"); + + // Apply additional shares on 20 May 2016 (and keep it active/approved) + applyAdditionalShares(shareAccountId, "20 May 2016", "30"); + + // Verify the transaction exists and is active + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + List> transactions = (List>) shareAccountData.get("purchasedShares"); + String transactionId = findTransactionId(transactions, "purchasedSharesType.purchased", "20 May 2016"); + Assertions.assertNotNull(transactionId, "Transaction for 20 May 2016 should exist"); + + // Try to close account on 15 May 2016 (before the active transaction) + // This should FAIL + Map closeAccountMap = new HashMap<>(); + closeAccountMap.put("note", "Share Account Close Note"); + closeAccountMap.put("dateFormat", "dd MMMM yyyy"); + closeAccountMap.put("closedDate", "15 May 2016"); + closeAccountMap.put("locale", "en"); + String closeJson = new Gson().toJson(closeAccountMap); + + ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + + try { + ShareAccountTransactionHelper.postCommand("close", shareAccountId, closeJson, requestSpec, errorResponse); + + // If we get here, the validation worked correctly (request was rejected) + // This is the expected behavior + } catch (Exception e) { + // Depending on the framework, the error might be thrown as an exception + // We expect this to fail, so this is acceptable + Assertions + .assertTrue( + e.getMessage().contains("chronological") || e.getMessage().contains("date") || e.getMessage().contains("before") + || e.getMessage().contains("transaction"), + "Error message should indicate chronological validation failure"); + } + + // Verify account is still active (not closed) + shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.active", String.valueOf(statusMap.get("code")), + "Account should still be active since closure was rejected"); + } + private Integer createShareProduct() { String shareProductJson = shareProductHelper.build(); return ShareProductTransactionHelper.createShareProduct(shareProductJson, requestSpec, responseSpec); @@ -1009,4 +1288,140 @@ private Map createCharge(final Integer chargeId, String amount) map.put("amount", amount); return map; } + + private void updateShareAccountWithInitialData(Integer shareAccountId, Integer requestedShares, String applicationDate) { + Map shareAccountDataForUpdate = new HashMap<>(); + shareAccountDataForUpdate.put("requestedShares", requestedShares); + shareAccountDataForUpdate.put("applicationDate", applicationDate); + shareAccountDataForUpdate.put("dateFormat", "dd MMMM yyyy"); + shareAccountDataForUpdate.put("locale", "en_GB"); + String updateShareAccountJsonString = new Gson().toJson(shareAccountDataForUpdate); + ShareAccountTransactionHelper.updateShareAccount(shareAccountId, updateShareAccountJsonString, requestSpec, responseSpec); + } + + private void approveShareAccount(Integer shareAccountId, String approvalDate) { + Map approveMap = new HashMap<>(); + approveMap.put("note", "Share Account Approval Note"); + approveMap.put("dateFormat", "dd MMMM yyyy"); + approveMap.put("approvedDate", approvalDate); + approveMap.put("locale", "en"); + String approve = new Gson().toJson(approveMap); + ShareAccountTransactionHelper.postCommand("approve", shareAccountId, approve, requestSpec, responseSpec); + } + + private void activateShareAccount(Integer shareAccountId, String activationDate) { + Map activateMap = new HashMap<>(); + activateMap.put("dateFormat", "dd MMMM yyyy"); + activateMap.put("activatedDate", activationDate); + activateMap.put("locale", "en"); + String activateJson = new Gson().toJson(activateMap); + ShareAccountTransactionHelper.postCommand("activate", shareAccountId, activateJson, requestSpec, responseSpec); + } + + private void applyAdditionalShares(Integer shareAccountId, String requestedDate, String requestedShares) { + Map additionalSharesRequestMap = new HashMap<>(); + additionalSharesRequestMap.put("requestedDate", requestedDate); + additionalSharesRequestMap.put("dateFormat", "dd MMMM yyyy"); + additionalSharesRequestMap.put("locale", "en"); + additionalSharesRequestMap.put("requestedShares", requestedShares); + String additionalSharesRequestJson = new Gson().toJson(additionalSharesRequestMap); + ShareAccountTransactionHelper.postCommand("applyadditionalshares", shareAccountId, additionalSharesRequestJson, requestSpec, + responseSpec); + } + + private String formatTransactionDate(List dateList, DateFormat formatter) { + Calendar cal = Calendar.getInstance(); + cal.set(dateList.get(0), dateList.get(1) - 1, dateList.get(2)); + Date date = cal.getTime(); + return formatter.format(date); + } + + private String findTransactionId(List> transactions, String transactionTypeCode, String expectedDate) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + return String.valueOf(transaction.get("id")); + } + } + return null; + } + + private void rejectAdditionalSharesRequest(Integer shareAccountId, String transactionId) { + Map>> rejectMap = new HashMap<>(); + List> list = new ArrayList<>(); + Map idsMap = new HashMap<>(); + idsMap.put("id", transactionId); + list.add(idsMap); + rejectMap.put("requestedShares", list); + String rejectJson = new Gson().toJson(rejectMap); + ShareAccountTransactionHelper.postCommand("rejectadditionalshares", shareAccountId, rejectJson, requestSpec, responseSpec); + } + + private void verifyTransactionStatus(List> transactions, String transactionTypeCode, String expectedDate, + String expectedStatus) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + boolean transactionFound = false; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + Map transactionStatusMap = (Map) transaction.get("status"); + Assertions.assertEquals(expectedStatus, String.valueOf(transactionStatusMap.get("code"))); + transactionFound = true; + break; + } + } + + Assertions.assertTrue(transactionFound, + String.format("Transaction with type %s for %s should exist", transactionTypeCode, expectedDate)); + } + + private void verifyTransactionWithShares(List> transactions, String transactionTypeCode, String expectedDate, + String expectedShares, String expectedStatus) { + DateFormat simple = new SimpleDateFormat("dd MMMM yyyy"); + boolean transactionFound = false; + + for (Map transaction : transactions) { + Map transactionTypeMap = (Map) transaction.get("type"); + List dateList = (List) transaction.get("purchasedDate"); + String transactionType = (String) transactionTypeMap.get("code"); + String transactionDate = formatTransactionDate(dateList, simple); + + if (transactionType.equals(transactionTypeCode) && transactionDate.equals(expectedDate)) { + Assertions.assertEquals(expectedShares, String.valueOf(transaction.get("numberOfShares"))); + Map transactionStatusMap = (Map) transaction.get("status"); + Assertions.assertEquals(expectedStatus, String.valueOf(transactionStatusMap.get("code"))); + transactionFound = true; + break; + } + } + + Assertions.assertTrue(transactionFound, String.format("Transaction for %s should be successfully created", expectedDate)); + } + + private Integer setupAndActivateShareAccount(Integer clientId, Integer productId, Integer savingsAccountId, String initialDate) { + final Integer shareAccountId = createShareAccount(clientId, productId, savingsAccountId); + Assertions.assertNotNull(shareAccountId); + + updateShareAccountWithInitialData(shareAccountId, 25, initialDate); + approveShareAccount(shareAccountId, initialDate); + activateShareAccount(shareAccountId, initialDate); + + // Verify account is active + Map shareAccountData = ShareAccountTransactionHelper.retrieveShareAccount(shareAccountId, requestSpec, + responseSpec); + Map statusMap = (Map) shareAccountData.get("status"); + Assertions.assertEquals("shareAccountStatusType.active", String.valueOf(statusMap.get("code"))); + + return shareAccountId; + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductHelper.java new file mode 100644 index 00000000000..74bf6e9517b --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductHelper.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.workingcapitalloanproduct; + +import java.util.List; +import org.apache.fineract.client.feign.util.FeignCalls; +import org.apache.fineract.client.models.DeleteWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.GetWorkingCapitalLoanProductsTemplateResponse; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsResponse; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdResponse; +import org.apache.fineract.integrationtests.common.FineractFeignClientHelper; + +public class WorkingCapitalLoanProductHelper { + + public WorkingCapitalLoanProductHelper() {} + + public PostWorkingCapitalLoanProductsResponse createWorkingCapitalLoanProduct(final PostWorkingCapitalLoanProductsRequest request) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .createWorkingCapitalLoanProduct(request)); + } + + public GetWorkingCapitalLoanProductsProductIdResponse retrieveWorkingCapitalLoanProductByExternalId(final String externalId) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .retrieveOneWorkingCapitalLoanProductByExternalId(externalId)); + } + + public GetWorkingCapitalLoanProductsProductIdResponse retrieveWorkingCapitalLoanProductById(final Long productId) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .retrieveOneWorkingCapitalLoanProduct(productId)); + } + + public List retrieveAllWorkingCapitalLoanProducts() { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .retrieveAllWorkingCapitalLoanProducts()); + } + + public GetWorkingCapitalLoanProductsTemplateResponse retrieveTemplate() { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .retrieveTemplateWorkingCapitalLoanProduct()); + } + + public PutWorkingCapitalLoanProductsProductIdResponse updateWorkingCapitalLoanProductByExternalId(final String externalId, + final PutWorkingCapitalLoanProductsProductIdRequest request) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .updateWorkingCapitalLoanProductByExternalId(externalId, request)); + } + + public PutWorkingCapitalLoanProductsProductIdResponse updateWorkingCapitalLoanProductById(final Long productId, + final PutWorkingCapitalLoanProductsProductIdRequest request) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .updateWorkingCapitalLoanProduct(productId, request)); + } + + public DeleteWorkingCapitalLoanProductsProductIdResponse deleteWorkingCapitalLoanProductByExternalId(final String externalId) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .deleteWorkingCapitalLoanProductByExternalId(externalId)); + } + + public DeleteWorkingCapitalLoanProductsProductIdResponse deleteWorkingCapitalLoanProductById(final Long productId) { + return FeignCalls.ok(() -> FineractFeignClientHelper.getFineractFeignClient().workingCapitalLoanProducts() + .deleteWorkingCapitalLoanProduct(productId)); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductTestBuilder.java new file mode 100644 index 00000000000..bfb0f25c2c5 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/workingcapitalloanproduct/WorkingCapitalLoanProductTestBuilder.java @@ -0,0 +1,350 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.workingcapitalloanproduct; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.feign.ObjectMapperFactory; +import org.apache.fineract.client.models.PostWorkingCapitalLoanProductsRequest; +import org.apache.fineract.client.models.PutWorkingCapitalLoanProductsProductIdRequest; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalAmortizationType; +import org.apache.fineract.portfolio.workingcapitalloanproduct.domain.WorkingCapitalLoanPeriodFrequencyType; + +@Slf4j +public class WorkingCapitalLoanProductTestBuilder { + + private static final String DEFAULT_NAME = "Test WCP Product"; + private static final String DEFAULT_SHORT_NAME = "TWCP"; + private static final String DEFAULT_CURRENCY_CODE = "USD"; + private static final Integer DEFAULT_DECIMAL_PLACE = 2; + private static final Integer DEFAULT_CURRENCY_IN_MULTIPLES_OF = 1; + private static final String DEFAULT_AMORTIZATION = WorkingCapitalAmortizationType.EIR.name(); + private static final Integer DEFAULT_NPV_DAY_COUNT = 360; + private static final BigDecimal DEFAULT_PRINCIPAL_AMOUNT = BigDecimal.valueOf(10000); + private static final BigDecimal DEFAULT_PERIOD_PAYMENT_RATE = BigDecimal.valueOf(1.0); + private static final Integer DEFAULT_PERIOD_PAYMENT_FREQUENCY = 30; + private static final String DEFAULT_PERIOD_PAYMENT_FREQUENCY_TYPE = WorkingCapitalLoanPeriodFrequencyType.DAYS.name(); + private static final List DEFAULT_PAYMENT_ALLOCATION_TYPES = List.of("PENALTY", "FEE", "PRINCIPAL"); + + private String name = DEFAULT_NAME; + private String shortName = DEFAULT_SHORT_NAME; + private String description; + private Long fundId; + private String externalId; + private String currencyCode = DEFAULT_CURRENCY_CODE; + private Integer decimalPlace = DEFAULT_DECIMAL_PLACE; + private Integer currencyInMultiplesOf = DEFAULT_CURRENCY_IN_MULTIPLES_OF; + private String amortizationType = DEFAULT_AMORTIZATION; + private BigDecimal flatPercentageAmount; + private Long delinquencyBucketId; + private Integer npvDayCount = DEFAULT_NPV_DAY_COUNT; + private BigDecimal principalAmountMin; + private BigDecimal principalAmountDefault = DEFAULT_PRINCIPAL_AMOUNT; + private BigDecimal principalAmountMax; + private BigDecimal minPeriodPaymentRate; + private BigDecimal periodPaymentRate = DEFAULT_PERIOD_PAYMENT_RATE; + private BigDecimal maxPeriodPaymentRate; + private BigDecimal discount; + private Integer repaymentEvery = DEFAULT_PERIOD_PAYMENT_FREQUENCY; + private String repaymentFrequencyType = DEFAULT_PERIOD_PAYMENT_FREQUENCY_TYPE; + private List paymentAllocationTypes = DEFAULT_PAYMENT_ALLOCATION_TYPES; + private Map allowAttributeOverrides; + + public WorkingCapitalLoanProductTestBuilder withName(final String name) { + this.name = name; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withShortName(final String shortName) { + this.shortName = shortName; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withDescription(final String description) { + this.description = description; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withFundId(final Long fundId) { + this.fundId = fundId; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withExternalId(final String externalId) { + this.externalId = externalId; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withCurrencyCode(final String currencyCode) { + this.currencyCode = currencyCode; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withDecimalPlace(final Integer decimalPlace) { + this.decimalPlace = decimalPlace; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withCurrencyInMultiplesOf(final Integer currencyInMultiplesOf) { + this.currencyInMultiplesOf = currencyInMultiplesOf; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withAmortizationType(final String amortizationType) { + this.amortizationType = amortizationType; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withFlatPercentageAmount(final BigDecimal flatPercentageAmount) { + this.flatPercentageAmount = flatPercentageAmount; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withDelinquencyBucketId(final Long delinquencyBucketId) { + this.delinquencyBucketId = delinquencyBucketId; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withNpvDayCount(final Integer npvDayCount) { + this.npvDayCount = npvDayCount; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withPaymentAllocationTypes(final List paymentAllocationTypes) { + this.paymentAllocationTypes = paymentAllocationTypes; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withPrincipalAmountMin(final BigDecimal principalAmountMin) { + this.principalAmountMin = principalAmountMin; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withPrincipalAmountDefault(final BigDecimal principalAmountDefault) { + this.principalAmountDefault = principalAmountDefault; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withPrincipalAmountMax(final BigDecimal principalAmountMax) { + this.principalAmountMax = principalAmountMax; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withMinPeriodPaymentRate(final BigDecimal minPeriodPaymentRate) { + this.minPeriodPaymentRate = minPeriodPaymentRate; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withPeriodPaymentRate(final BigDecimal periodPaymentRate) { + this.periodPaymentRate = periodPaymentRate; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withMaxPeriodPaymentRate(final BigDecimal maxPeriodPaymentRate) { + this.maxPeriodPaymentRate = maxPeriodPaymentRate; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withDiscount(final BigDecimal discount) { + this.discount = discount; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withRepaymentEvery(final Integer repaymentEvery) { + this.repaymentEvery = repaymentEvery; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withRepaymentFrequencyType(final String repaymentFrequencyType) { + this.repaymentFrequencyType = repaymentFrequencyType; + return this; + } + + public WorkingCapitalLoanProductTestBuilder withAllowAttributeOverrides(final Map allowAttributeOverrides) { + this.allowAttributeOverrides = allowAttributeOverrides; + return this; + } + + public PostWorkingCapitalLoanProductsRequest build() { + final PostWorkingCapitalLoanProductsRequest request = new PostWorkingCapitalLoanProductsRequest(); + populateCommonFields(request); + setPaymentAllocation(request); + setAllowAttributeOverrides(request); + return request; + } + + public PutWorkingCapitalLoanProductsProductIdRequest buildUpdateRequest() { + final PutWorkingCapitalLoanProductsProductIdRequest request = new PutWorkingCapitalLoanProductsProductIdRequest(); + populateCommonFields(request); + setPaymentAllocation(request); + setAllowAttributeOverrides(request); + return request; + } + + private void populateCommonFields(final PostWorkingCapitalLoanProductsRequest request) { + request.setName(this.name); + request.setShortName(this.shortName); + request.setDescription(this.description); + request.setFundId(this.fundId); + request.setExternalId(this.externalId); + request.setCurrencyCode(this.currencyCode); + request.setDigitsAfterDecimal(this.decimalPlace); + request.setInMultiplesOf(this.currencyInMultiplesOf); + if (this.amortizationType != null) { + request.setAmortizationType(PostWorkingCapitalLoanProductsRequest.AmortizationTypeEnum.valueOf(this.amortizationType)); + } + request.setFlatPercentageAmount(this.flatPercentageAmount); + request.setDelinquencyBucketId(this.delinquencyBucketId); + request.setNpvDayCount(this.npvDayCount); + request.setMinPrincipal(this.principalAmountMin); + request.setPrincipal(this.principalAmountDefault); + request.setMaxPrincipal(this.principalAmountMax); + request.setMinPeriodPaymentRate(this.minPeriodPaymentRate); + request.setPeriodPaymentRate(this.periodPaymentRate); + request.setMaxPeriodPaymentRate(this.maxPeriodPaymentRate); + request.setDiscount(this.discount); + request.setRepaymentEvery(this.repaymentEvery); + if (this.repaymentFrequencyType != null) { + request.setRepaymentFrequencyType( + PostWorkingCapitalLoanProductsRequest.RepaymentFrequencyTypeEnum.valueOf(this.repaymentFrequencyType)); + } + request.setLocale("en_US"); + request.setDateFormat("yyyy-MM-dd"); + } + + private void populateCommonFields(final PutWorkingCapitalLoanProductsProductIdRequest request) { + request.setName(this.name); + request.setShortName(this.shortName); + request.setDescription(this.description); + request.setFundId(this.fundId); + request.setCurrencyCode(this.currencyCode); + request.setDigitsAfterDecimal(this.decimalPlace); + request.setInMultiplesOf(this.currencyInMultiplesOf); + if (this.amortizationType != null) { + request.setAmortizationType(PutWorkingCapitalLoanProductsProductIdRequest.AmortizationTypeEnum.valueOf(this.amortizationType)); + } + request.setFlatPercentageAmount(this.flatPercentageAmount); + request.setDelinquencyBucketId(this.delinquencyBucketId); + request.setNpvDayCount(this.npvDayCount); + request.setMinPrincipal(this.principalAmountMin); + request.setPrincipal(this.principalAmountDefault); + request.setMaxPrincipal(this.principalAmountMax); + request.setMinPeriodPaymentRate(this.minPeriodPaymentRate); + request.setPeriodPaymentRate(this.periodPaymentRate); + request.setMaxPeriodPaymentRate(this.maxPeriodPaymentRate); + request.setDiscount(this.discount); + request.setRepaymentEvery(this.repaymentEvery); + if (this.repaymentFrequencyType != null) { + request.setRepaymentFrequencyType( + PutWorkingCapitalLoanProductsProductIdRequest.RepaymentFrequencyTypeEnum.valueOf(this.repaymentFrequencyType)); + } + request.setLocale("en_US"); + request.setDateFormat("yyyy-MM-dd"); + } + + private void setPaymentAllocation(final PostWorkingCapitalLoanProductsRequest request) { + setPaymentAllocation(request, PostWorkingCapitalLoanProductsRequest.class); + } + + private void setPaymentAllocation(final PutWorkingCapitalLoanProductsProductIdRequest request) { + setPaymentAllocation(request, PutWorkingCapitalLoanProductsProductIdRequest.class); + } + + private void setPaymentAllocation(final T request, final Class requestClass) { + if (this.paymentAllocationTypes != null && !this.paymentAllocationTypes.isEmpty()) { + try { + final ObjectMapper objectMapper = ObjectMapperFactory.getShared(); + final String requestJson = objectMapper.writeValueAsString(request); + final ObjectNode requestNode = (ObjectNode) objectMapper.readTree(requestJson); + final ArrayNode paymentAllocationArray = objectMapper.createArrayNode(); + final ObjectNode paymentAllocationNode = objectMapper.createObjectNode(); + paymentAllocationNode.put("transactionType", "DEFAULT"); + final ArrayNode paymentAllocationOrderArray = objectMapper.createArrayNode(); + int order = 1; + for (final String allocationType : this.paymentAllocationTypes) { + final ObjectNode orderItem = objectMapper.createObjectNode(); + orderItem.put("paymentAllocationRule", allocationType); + orderItem.put("order", order++); + paymentAllocationOrderArray.add(orderItem); + } + paymentAllocationNode.set("paymentAllocationOrder", paymentAllocationOrderArray); + paymentAllocationArray.add(paymentAllocationNode); + requestNode.set("paymentAllocation", paymentAllocationArray); + final T updatedRequest = objectMapper.treeToValue(requestNode, requestClass); + copyAllFields(updatedRequest, request, requestClass); + } catch (final Exception e) { + throw new IllegalStateException("Failed to set paymentAllocation", e); + } + } + } + + private void setAllowAttributeOverrides(final PostWorkingCapitalLoanProductsRequest request) { + setAllowAttributeOverrides(request, PostWorkingCapitalLoanProductsRequest.class); + } + + private void setAllowAttributeOverrides(final PutWorkingCapitalLoanProductsProductIdRequest request) { + setAllowAttributeOverrides(request, PutWorkingCapitalLoanProductsProductIdRequest.class); + } + + private void setAllowAttributeOverrides(final T request, final Class requestClass) { + if (this.allowAttributeOverrides == null || this.allowAttributeOverrides.isEmpty()) { + return; + } + + try { + final ObjectMapper objectMapper = ObjectMapperFactory.getShared(); + final String requestJson = objectMapper.writeValueAsString(request); + final ObjectNode requestNode = (ObjectNode) objectMapper.readTree(requestJson); + final ObjectNode allowOverridesNode = objectMapper.createObjectNode(); + for (final Map.Entry entry : this.allowAttributeOverrides.entrySet()) { + allowOverridesNode.put(entry.getKey(), entry.getValue()); + } + requestNode.set("allowAttributeOverrides", allowOverridesNode); + final T updatedRequest = objectMapper.treeToValue(requestNode, requestClass); + copyAllFields(updatedRequest, request, requestClass); + } catch (final Exception e) { + throw new IllegalStateException("Failed to set allowAttributeOverrides", e); + } + } + + private void copyAllFields(final T source, final T target, final Class clazz) { + final Field[] fields = clazz.getDeclaredFields(); + for (final Field field : fields) { + try { + if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) { + continue; + } + field.setAccessible(true); + final Object value = field.get(source); + if (value != null) { + field.set(target, value); + } + } catch (final IllegalAccessException e) { + log.warn("Failed to copy field {}: {}", field.getName(), e.getMessage()); + } + } + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorHelper.java index 6ba5a231a01..30423314352 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorHelper.java @@ -51,6 +51,25 @@ public Integer createGuarantor(final Integer loanId, final String guarantorJSON) CommonConstants.RESPONSE_RESOURCE_ID); } + // TODO: Rewrite to use fineract-client instead! + // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // org.apache.fineract.client.models.PostLoansLoanIdRequest) + @Deprecated(forRemoval = true) + public Object createGuarantorWithError(final Integer loanId, final String guarantorJSON, final RequestSpecification requestSpec, + final ResponseSpecification responseSpec) { + return Utils.performServerPost(requestSpec, responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, guarantorJSON, + CommonConstants.RESPONSE_ERROR); + } + + // TODO: Rewrite to use fineract-client instead! + // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // org.apache.fineract.client.models.PostLoansLoanIdRequest) + @Deprecated(forRemoval = true) + public java.util.ArrayList getGuarantorList(final Integer loanId) { + return (java.util.ArrayList) Utils.performServerGet(this.requestSpec, this.responseSpec, + LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, ""); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTestBuilder.java index 0b793373dc1..ef2e5707d6c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTestBuilder.java @@ -31,6 +31,7 @@ public class GuarantorTestBuilder { @SuppressWarnings("unused") private static final String GUARANTOR_TYPE_STAFF = "2"; private static final String GUARANTOR_TYPE_EXTERNAL = "3"; + private static final String GUARANTOR_TYPE_GROUP = "4"; private String guarantorTypeId = "1"; private String entityId = null; @@ -55,7 +56,7 @@ public String build() { map.put("state", state); map.put("zip", zip); - } else if (GUARANTOR_TYPE_CUSTOMER.equals(guarantorTypeId)) { + } else if (GUARANTOR_TYPE_CUSTOMER.equals(guarantorTypeId) || GUARANTOR_TYPE_GROUP.equals(guarantorTypeId)) { map.put("entityId", entityId); map.put("amount", guaranteeAmount); map.put("savingsId", savingsId); @@ -81,6 +82,15 @@ public GuarantorTestBuilder existingCustomerWithoutGuaranteeAmount(final String return this; } + public GuarantorTestBuilder existingGroupWithGuaranteeAmount(final String entityId, final String savingsId, + final String guaranteeAmount) { + this.entityId = entityId; + this.savingsId = savingsId; + this.guaranteeAmount = guaranteeAmount; + this.guarantorTypeId = GUARANTOR_TYPE_GROUP; + return this; + } + public GuarantorTestBuilder externalCustomer() { this.guarantorTypeId = GUARANTOR_TYPE_EXTERNAL; return this; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java index 2b2c728aff2..a258b0c0fd5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java @@ -24,12 +24,15 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.restassured.path.json.JsonPath; import java.math.BigDecimal; import java.time.LocalDate; import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PostChargesResponse; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; @@ -1024,6 +1027,250 @@ public void test_LoanReAgeTransactionWithTransactionAmount() { }); } + @Test + public void test_LoanReAge_RepeatedReAgeDoesNotCreateDuplicatePeriods() { + AtomicLong createdLoanId = new AtomicLong(); + + runAt("28 January 2026", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 6; + int repaymentEvery = 1; + + // Create interest-bearing progressive loan product + PostLoanProductsRequest product = create4IProgressive() // + .numberOfRepayments(numberOfRepayments) // + .repaymentEvery(repaymentEvery) // + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L); // + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + // Apply and Approve Loan + double amount = 1000.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "28 January 2026", amount, numberOfRepayments)// + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)// + .interestRatePerPeriod(BigDecimal.valueOf(10.0))// + .interestCalculationPeriodType(InterestCalculationPeriodType.DAILY); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "28 January 2026")); + + Long loanId = approvedLoanResult.getLoanId(); + + // Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(amount), "28 January 2026"); + + createdLoanId.set(loanId); + + // First re-age + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + // Second re-age on next day + runAt("29 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + // Third re-age on next day + runAt("30 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + // Fourth re-age on next day + runAt("31 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + + // Verify: should have 8 periods total (1 disbursement + 1 stub + 6 re-aged installments) + // NOT 12+ periods with spurious stubs from each intermediate reAge + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + List periods = loanDetails.getRepaymentSchedule().getPeriods(); + + assertEquals(8, periods.size(), "Expected 8 periods (1 disbursement + 1 stub + 6 re-aged) but got " + periods.size()); + + // Verify due dates are correct + assertEquals(LocalDate.of(2026, 1, 28), periods.get(0).getDueDate()); // disbursement + assertEquals(LocalDate.of(2026, 1, 31), periods.get(1).getDueDate()); // stub + assertEquals(LocalDate.of(2026, 2, 28), periods.get(2).getDueDate()); // 1st re-aged + assertEquals(LocalDate.of(2026, 3, 28), periods.get(3).getDueDate()); // 2nd re-aged + assertEquals(LocalDate.of(2026, 4, 28), periods.get(4).getDueDate()); // 3rd re-aged + assertEquals(LocalDate.of(2026, 5, 28), periods.get(5).getDueDate()); // 4th re-aged + assertEquals(LocalDate.of(2026, 6, 28), periods.get(6).getDueDate()); // 5th re-aged + assertEquals(LocalDate.of(2026, 7, 28), periods.get(7).getDueDate()); // 6th re-aged + + checkMaturityDates(loanId, LocalDate.of(2026, 7, 28), LocalDate.of(2026, 7, 28)); + }); + } + + @Test + public void test_LoanReAge_RepeatedReAge_COBAccrualDoesNotFail() { + AtomicLong createdLoanId = new AtomicLong(); + + runAt("28 January 2026", () -> { + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 6; + int repaymentEvery = 1; + + PostLoanProductsRequest product = create4IProgressive() // + .numberOfRepayments(numberOfRepayments) // + .repaymentEvery(repaymentEvery) // + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L); // + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + double amount = 1000.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "28 January 2026", amount, numberOfRepayments)// + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)// + .interestRatePerPeriod(BigDecimal.valueOf(10.0))// + .interestCalculationPeriodType(InterestCalculationPeriodType.DAILY); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "28 January 2026")); + + Long loanId = approvedLoanResult.getLoanId(); + + disburseLoan(loanId, BigDecimal.valueOf(amount), "28 January 2026"); + + createdLoanId.set(loanId); + + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("29 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("30 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("31 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("01 February 2026", () -> { + long loanId = createdLoanId.get(); + + // Execute inline COB - this should not fail with NoSuchElementException + executeInlineCOB(loanId); + + // Verify loan schedule still has 8 periods (1 disbursement + 1 stub + 6 re-aged) + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + List periods = loanDetails.getRepaymentSchedule().getPeriods(); + + assertEquals(8, periods.size(), "Expected 8 periods (1 disbursement + 1 stub + 6 re-aged) but got " + periods.size()); + + // Verify loan is still active (COB did not crash) + assertEquals(LoanStatus.ACTIVE.getValue(), loanDetails.getStatus().getId().intValue()); + }); + } + + @Test + public void test_LoanReAge_RepeatedReAge_PreviewShowsCorrectPeriods() { + AtomicLong createdLoanId = new AtomicLong(); + + runAt("28 January 2026", () -> { + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 6; + int repaymentEvery = 1; + + PostLoanProductsRequest product = create4IProgressive() // + .numberOfRepayments(numberOfRepayments) // + .repaymentEvery(repaymentEvery) // + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L); // + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + Long loanProductId = loanProductResponse.getResourceId(); + + double amount = 1000.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "28 January 2026", amount, numberOfRepayments)// + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)// + .interestRatePerPeriod(BigDecimal.valueOf(10.0))// + .interestCalculationPeriodType(InterestCalculationPeriodType.DAILY); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + + PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), + approveLoanRequest(amount, "28 January 2026")); + + Long loanId = approvedLoanResult.getLoanId(); + + disburseLoan(loanId, BigDecimal.valueOf(amount), "28 January 2026"); + + createdLoanId.set(loanId); + + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("29 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("30 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + }); + + runAt("31 January 2026", () -> { + long loanId = createdLoanId.get(); + reAgeLoan(loanId, RepaymentFrequencyType.MONTHS_STRING, 1, "28 February 2026", 6, null); + + // Verify actual schedule has 8 periods + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + List periods = loanDetails.getRepaymentSchedule().getPeriods(); + + assertEquals(8, periods.size(), "Expected 8 periods (1 disbursement + 1 stub + 6 re-aged) but got " + periods.size()); + }); + + runAt("01 February 2026", () -> { + long loanId = createdLoanId.get(); + + // Call preview API via REST + String previewUrl = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/reage-preview" // + + "?frequencyType=MONTHS&frequencyNumber=1&startDate=28+February+2026&numberOfInstallments=6" // + + "&dateFormat=dd+MMMM+yyyy&locale=en&" + Utils.TENANT_IDENTIFIER; + + String jsonResponse = Utils.performServerGet(requestSpec, responseSpec, previewUrl); + + // Parse the periods array from the JSON response + List> previewPeriods = JsonPath.from(jsonResponse).getList("periods"); + + assertNotNull(previewPeriods, "Preview response should contain periods"); + assertEquals(8, previewPeriods.size(), + "Preview should have 8 periods (1 disbursement + 1 stub + 6 re-aged) but got " + previewPeriods.size()); + }); + } + private HashMap getReAgeTemplate(Long loanId) { final String GET_REAGE_TEMPLATE_URL = "/fineract-provider/api/v1/loans/" + loanId + "/transactions/template?command=reAge&" + Utils.TENANT_IDENTIFIER; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java index 5236d018424..d1024edd8cc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java @@ -24,8 +24,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.util.HashMap; import java.util.concurrent.atomic.AtomicLong; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansRequest; @@ -33,7 +35,9 @@ import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.BaseLoanIntegrationTest; import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.portfolio.loanaccount.domain.reamortization.LoanReAmortizationInterestHandlingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; @@ -998,6 +1002,86 @@ public void reAmortizationOnDisbursementDayEqualInterestSplitTest() { }); } + @Test + public void reAmortizationEqualInterestSplitWithFeeChargePayoffTest() { + runAt("01 January 2024", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + int numberOfRepayments = 6; + int repaymentEvery = 1; + + // Create Interest-Bearing Loan Product with 7% interest (progressive schedule) + Long loanProductId = createInterestBearingProgressiveLoanProduct(numberOfRepayments, repaymentEvery); + + // Apply for loan with 200 amount (will approve with 100) + double applyAmount = 200.0; + double approveAmount = 100.0; + double disburseAmount = 100.0; + + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2024", applyAmount, + numberOfRepayments)// + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .repaymentEvery(repaymentEvery)// + .loanTermFrequency(numberOfRepayments)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS)// + .loanTermFrequencyType(RepaymentFrequencyType.MONTHS)// + .interestRatePerPeriod(BigDecimal.valueOf(7.0))// + .interestCalculationPeriodType(InterestCalculationPeriodType.DAILY); + + PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + loanId.set(postLoansResponse.getLoanId()); + + // Approve with 100 (partial approval) + loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(approveAmount, "01 January 2024")); + + // Disburse 100 on Jan 1, 2024 + disburseLoan(loanId.get(), BigDecimal.valueOf(disburseAmount), "01 January 2024"); + + // Verify disbursement transaction + verifyTransactions(loanId.get(), // + transaction(100.0, "Disbursement", "01 January 2024") // + ); + }); + + // Make repayment on Feb 1, 2024 and add fee charge due Feb 15, 2024 + runAt("01 February 2024", () -> { + loanTransactionHelper.makeLoanRepayment("01 February 2024", 17.01f, (int) loanId.get()); + + addChargeWithCurrency(loanId.get(), false, 10.0, "15 February 2024", "EUR"); + + verifyTransactions(loanId.get(), // + transaction(100.0, "Disbursement", "01 January 2024"), // + transaction(17.01, "Repayment", "01 February 2024") // + ); + }); + + // Re-amortize with EQUAL_AMORTIZATION_INTEREST_SPLIT on Mar 15, 2024 + runAt("15 March 2024", () -> { + reAmortizeLoan(loanId.get(), LoanReAmortizationInterestHandlingType.EQUAL_AMORTIZATION_INTEREST_SPLIT.name()); + + verifyTransactions(loanId.get(), // + transaction(100.0, "Disbursement", "01 January 2024"), // + transaction(17.01, "Repayment", "01 February 2024"), // + transaction(17.01, "Re-amortize", "15 March 2024") // + ); + + // Pay-off the loan on Mar 15, 2024 + HashMap prepayAmount = loanTransactionHelper.getPrepayAmount(requestSpec, responseSpec, (int) loanId.get()); + assertNotNull(prepayAmount); + Float amount = (Float) prepayAmount.get("amount"); + + loanTransactionHelper.makeLoanRepayment("15 March 2024", amount, (int) loanId.get()); + + // Verify loan is closed with all obligations met (status 600) + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.get()); + assertNotNull(loanDetails.getStatus()); + assertEquals(600, loanDetails.getStatus().getId().intValue(), + "Loan should be CLOSED_OBLIGATIONS_MET (600) after pay-off but was " + loanDetails.getStatus().getId()); + assertTrue(loanDetails.getStatus().getClosedObligationsMet(), "Loan status should be closedObligationsMet after pay-off"); + }); + } + private Long createInterestBearingProgressiveLoanProduct(int numberOfRepayments, int repaymentEvery) { PostLoanProductsRequest product = create4IProgressive()// .numberOfRepayments(numberOfRepayments)// @@ -1042,4 +1126,14 @@ private Long createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayme assertNotNull(getLoanProductsProductIdResponse); return loanProductResponse.getResourceId(); } + + private Long addChargeWithCurrency(Long loanId, boolean isPenalty, double amount, String dueDate, String currencyCode) { + Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper + .getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(amount), isPenalty, currencyCode)); + assertNotNull(chargeId); + Integer loanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId.intValue(), + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(chargeId), dueDate, String.valueOf(amount))); + assertNotNull(loanChargeId); + return loanChargeId.longValue(); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/AccountTransferWithdrawalFeeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/AccountTransferWithdrawalFeeTest.java index 4e496521bcf..105af2599b3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/AccountTransferWithdrawalFeeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/AccountTransferWithdrawalFeeTest.java @@ -87,19 +87,21 @@ public void testFromSavingsToSavingsAccountTransferWithWithdrawalFee() { // Perform transfer - without null-checks in SavingsAccount.payWithdrawalFee(), // this would trigger NPE because paymentDetail is null during account transfers - ok(fineractClient().accountTransfers.create4(new AccountTransferRequest().fromClientId(String.valueOf(fromClientId)) + ok(fineractClient().accountTransfers.createAccountTransfer(new AccountTransferRequest().fromClientId(String.valueOf(fromClientId)) .fromAccountId(String.valueOf(fromSavingsId)).fromAccountType("2").fromOfficeId("1").toClientId(String.valueOf(toClientId)) .toAccountId(String.valueOf(toSavingsId)).toAccountType("2").toOfficeId("1").transferDate(TRANSFER_DATE) .transferAmount(ACCOUNT_TRANSFER_AMOUNT).transferDescription("Transfer").dateFormat(DATETIME_PATTERN).locale("en_GB"))); // Verify balances: charge has no payment type binding, so it applies as normal withdrawal fee // from = 30000 - 15000 (transfer) - 100 (withdrawal fee) = 14900 - SavingsAccountData fromSavingsAccount = ok(fineractClient().savingsAccounts.retrieveOne26(fromSavingsId, false, null, "summary")); + SavingsAccountData fromSavingsAccount = ok( + fineractClient().savingsAccounts.retrieveSavingsAccount(fromSavingsId, false, null, "summary")); Assertions.assertEquals(BigDecimal.valueOf(14900).setScale(0), fromSavingsAccount.getSummary().getAccountBalance().setScale(0), "Verifying From Savings Account Balance after Account Transfer with Withdrawal Fee"); // to = 0 + 15000 (transfer) = 15000 - SavingsAccountData toSavingsAccount = ok(fineractClient().savingsAccounts.retrieveOne26(toSavingsId, false, null, "summary")); + SavingsAccountData toSavingsAccount = ok( + fineractClient().savingsAccounts.retrieveSavingsAccount(toSavingsId, false, null, "summary")); Assertions.assertEquals(BigDecimal.valueOf(15000).setScale(0), toSavingsAccount.getSummary().getAccountBalance().setScale(0), "Verifying To Savings Account Balance after Account Transfer"); } @@ -155,7 +157,7 @@ public void testFromSavingsToSavingsAccountTransferWithPaymentTypeWithdrawalFee( // Perform transfer - without null-checks in SavingsAccount.payWithdrawalFee(), // this throws NPE because paymentDetail is null and code tries to call // paymentDetail.getPaymentType().getName() - ok(fineractClient().accountTransfers.create4(new AccountTransferRequest().fromClientId(String.valueOf(fromClientId)) + ok(fineractClient().accountTransfers.createAccountTransfer(new AccountTransferRequest().fromClientId(String.valueOf(fromClientId)) .fromAccountId(String.valueOf(fromSavingsId)).fromAccountType("2").fromOfficeId("1").toClientId(String.valueOf(toClientId)) .toAccountId(String.valueOf(toSavingsId)).toAccountType("2").toOfficeId("1").transferDate(TRANSFER_DATE) .transferAmount(ACCOUNT_TRANSFER_AMOUNT).transferDescription("Transfer").dateFormat(DATETIME_PATTERN).locale("en_GB"))); @@ -163,12 +165,14 @@ public void testFromSavingsToSavingsAccountTransferWithPaymentTypeWithdrawalFee( // The payment-type fee is not applied because paymentDetail is null during transfer // and the null-check skips the payment type matching // from = 30000 - 15000 = 15000 - SavingsAccountData fromSavingsAccount = ok(fineractClient().savingsAccounts.retrieveOne26(fromSavingsId, false, null, "summary")); + SavingsAccountData fromSavingsAccount = ok( + fineractClient().savingsAccounts.retrieveSavingsAccount(fromSavingsId, false, null, "summary")); Assertions.assertEquals(BigDecimal.valueOf(15000).setScale(0), fromSavingsAccount.getSummary().getAccountBalance().setScale(0), "Verifying From Savings Account Balance after Account Transfer with Payment Type Withdrawal Fee"); // to = 0 + 15000 = 15000 - SavingsAccountData toSavingsAccount = ok(fineractClient().savingsAccounts.retrieveOne26(toSavingsId, false, null, "summary")); + SavingsAccountData toSavingsAccount = ok( + fineractClient().savingsAccounts.retrieveSavingsAccount(toSavingsId, false, null, "summary")); Assertions.assertEquals(BigDecimal.valueOf(15000).setScale(0), toSavingsAccount.getSummary().getAccountBalance().setScale(0), "Verifying To Savings Account Balance after Account Transfer"); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/base/BaseSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/base/BaseSavingsIntegrationTest.java index c772509cafd..fb967b5fefc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/base/BaseSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/savings/base/BaseSavingsIntegrationTest.java @@ -92,6 +92,7 @@ protected void runFromToInclusive(String fromDate, String toDate, Consumer runnable.run()); } @@ -129,7 +130,7 @@ protected PostSavingsProductsRequest dailyInterestPostingProduct() { } protected PostSavingsProductsResponse createProduct(PostSavingsProductsRequest productsRequest) { - return ok(fineractClient().savingsProducts.create14(productsRequest)); + return ok(fineractClient().savingsProducts.createSavingsProduct(productsRequest)); } protected PostSavingsAccountsRequest applySavingsRequest(Long clientId, Long productId, String submittedDate) { @@ -138,19 +139,19 @@ protected PostSavingsAccountsRequest applySavingsRequest(Long clientId, Long pro } protected PostSavingsAccountsResponse applySavingsAccount(PostSavingsAccountsRequest request) { - return ok(fineractClient().savingsAccounts.submitApplication2(request)); + return ok(fineractClient().savingsAccounts.submitSavingsApplication(request)); } protected PostSavingsAccountsAccountIdResponse approveSavingsAccount(Long savingsId, String date) { PostSavingsAccountsAccountIdRequest request = new PostSavingsAccountsAccountIdRequest().dateFormat(DATETIME_PATTERN).locale("en") .approvedOnDate(date); - return ok(fineractClient().savingsAccounts.handleCommands6(savingsId, request, "approve")); + return ok(fineractClient().savingsAccounts.handleCommandsSavingsAccount(savingsId, request, "approve")); } protected PostSavingsAccountsAccountIdResponse activateSavingsAccount(Long savingsId, String date) { PostSavingsAccountsAccountIdRequest request = new PostSavingsAccountsAccountIdRequest().dateFormat(DATETIME_PATTERN).locale("en") .activatedOnDate(date); - return ok(fineractClient().savingsAccounts.handleCommands6(savingsId, request, "activate")); + return ok(fineractClient().savingsAccounts.handleCommandsSavingsAccount(savingsId, request, "activate")); } protected PostSavingsAccountTransactionsResponse deposit(Long savingsId, String date, BigDecimal amount) { @@ -159,15 +160,15 @@ protected PostSavingsAccountTransactionsResponse deposit(Long savingsId, String .locale("en") // .paymentTypeId(1).transactionAmount(amount) // .transactionDate(date); // - return ok(fineractClient().savingsTransactions.transaction2(savingsId, request, "deposit")); + return ok(fineractClient().savingsTransactions.createSavingsAccountTransaction(savingsId, request, "deposit")); } protected SavingsAccountData getSavingsAccount(Long savingsId) { - return ok(fineractClient().savingsAccounts.retrieveOne26(savingsId, false, null, "transactions")); + return ok(fineractClient().savingsAccounts.retrieveSavingsAccount(savingsId, false, null, "transactions")); } protected List getTransactions(Long savingsId) { - return ok(fineractClient().savingsAccounts.retrieveOne26(savingsId, false, null, "transactions")).getTransactions(); + return ok(fineractClient().savingsAccounts.retrieveSavingsAccount(savingsId, false, null, "transactions")).getTransactions(); } protected void verifyNoTransactions(Long savingsId) { @@ -175,7 +176,8 @@ protected void verifyNoTransactions(Long savingsId) { } protected void verifyTransactions(Long savingsId, Transaction... transactions) { - SavingsAccountData savingsDetails = ok(fineractClient().savingsAccounts.retrieveOne26(savingsId, false, null, "transactions")); + SavingsAccountData savingsDetails = ok( + fineractClient().savingsAccounts.retrieveSavingsAccount(savingsId, false, null, "transactions")); if (transactions == null || transactions.length == 0) { Assertions.assertTrue(savingsDetails.getTransactions().isEmpty(), "No transaction is expected on savings account " + savingsId); } else { diff --git a/kubernetes/fineractmysql-deployment.yml b/kubernetes/fineractmysql-deployment.yml index c9da4518f74..3f94876db06 100644 --- a/kubernetes/fineractmysql-deployment.yml +++ b/kubernetes/fineractmysql-deployment.yml @@ -86,8 +86,10 @@ spec: tier: fineractmysql spec: containers: - - image: mariadb:11.4 + - image: mariadb:12.2 name: mysql + args: + - --innodb-snapshot-isolation=OFF resources: requests: memory: "1Gi" diff --git a/oauth2-tests/dependencies.gradle b/oauth2-tests/dependencies.gradle index 2b94eb21aef..c66dfb9a2b9 100644 --- a/oauth2-tests/dependencies.gradle +++ b/oauth2-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.47@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.49@zip' testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-provider', configuration: 'runtimeElements'), project(path: ':fineract-security', configuration: 'runtimeElements'), diff --git a/renovate.json b/renovate.json index db1a41f9e48..11c846edc6f 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,5 @@ { - "commitMessagePrefix": "FINERACT-2181: ", + "commitMessagePrefix": "FINERACT-2421: ", "extends": [ "config:base", "group:allNonMajor", @@ -35,46 +35,6 @@ "matchPackageNames": ["org.openapi.generator"], "allowedVersions": "<=7.8.0" }, - { - "matchPackageNames": ["org.eclipse.persistence:eclipselink"], - "allowedVersions": "<=4.0.2", - "description": "EclipseLink 4.0.1 has a bug which has been fixed but haven't been released. Try 4.0.2. https://github.com/eclipse-ee4j/eclipselink/issues/1832" - }, - { - "matchPackageNames": ["org.eclipse.persistence:org.eclipse.persistence.jpa"], - "allowedVersions": "<=4.0.2", - "description": "EclipseLink 4.0.1 has a bug which has been fixed but haven't been released. Try 4.0.2. https://github.com/eclipse-ee4j/eclipselink/issues/1832" - }, - { - "matchPackageNames": ["com.google.guava:guava"], - "allowedVersions": "<=32.0.0-jre", - "description": "Guava 31.1.1-jre has an issue that IntelliJ can't import the project anymore. Unsure why it's happening." - }, - { - "matchPackageNames": ["io.swagger.core.v3:swagger-annotations-jakarta"], - "allowedVersions": "<=2.2.11", - "description": "2.2.14 version is not compatible with the org.openapi.generator Gradle plugin version 6.6.0" - }, - { - "matchPackageNames": ["io.swagger.core.v3:swagger-jaxrs2-jakarta"], - "allowedVersions": "<=2.2.11", - "description": "2.2.14 version is not compatible with the org.openapi.generator Gradle plugin version 6.6.0" - }, - { - "matchPackageNames": ["io.swagger.core.v3:swagger-core-jakarta"], - "allowedVersions": "<=2.2.11", - "description": "2.2.14 version is not compatible with the org.openapi.generator Gradle plugin version 6.6.0" - }, - { - "matchPackageNames": ["io.swagger.core.v3.swagger-gradle-plugin"], - "allowedVersions": "<=2.2.11", - "description": "2.2.14 version is not compatible with the org.openapi.generator Gradle plugin version 6.6.0" - }, - { - "matchPackageNames": ["org.postgresql:postgresql"], - "allowedVersions": "<=42.7.3", - "description": "Postgres JDBC driver 42.7.5 has a performance bug: https://github.com/pgjdbc/pgjdbc/issues/3511#issuecomment-2637277977" - }, { "matchPackageNames": ["org.liquibase:liquibase-core", "org.liquibase.ext:liquibase-postgresql"], "allowedVersions": "<=4.33.0", diff --git a/settings.gradle b/settings.gradle index 07151345a06..9768ec5a37c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -64,6 +64,7 @@ include ':fineract-loan-origination' include ':fineract-loan' include ':fineract-savings' include ':fineract-report' +include ':fineract-mix' include ':fineract-war' include ':integration-tests' include ':twofactor-tests' diff --git a/twofactor-tests/dependencies.gradle b/twofactor-tests/dependencies.gradle index 9a5e31684cf..30f99d7a439 100644 --- a/twofactor-tests/dependencies.gradle +++ b/twofactor-tests/dependencies.gradle @@ -20,7 +20,7 @@ dependencies { // testCompile dependencies are ONLY used in src/test, not src/main. // Do NOT repeat dependencies which are ALREADY in implementation or runtimeOnly! // - tomcat 'org.apache.tomcat:tomcat:10.1.47@zip' + tomcat 'org.apache.tomcat:tomcat:10.1.49@zip' testImplementation( files("$rootDir/fineract-provider/build/classes/java/main/"), project(path: ':fineract-provider', configuration: 'runtimeElements'), 'org.junit.jupiter:junit-jupiter-api',