Add customer search and sponsor DD reconciliation modules; stop tracking logs
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s

Adds customersearch and sponsordd portfolio modules with associated
changelog parts, updates cashier deposit request handling, and stops
tracking fineract-provider/logs/.
This commit is contained in:
2026-07-22 11:53:32 +05:30
parent b064ee6307
commit 84b1685873
79 changed files with 7761 additions and 310107 deletions
+3 -1
View File
@@ -34,4 +34,6 @@ gradleExp/
.run/
.java-version
.windsurf/
.windsurf/
fineract-provider/logs/
-975
View File
@@ -1,975 +0,0 @@
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Configure project :
matching ref: COMMIT - 7dea88d30f72365945bed2160d2ba72dccdc80b7
ref configuration: COMMIT - pattern: null
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
describeTagPattern: .*(\d+\.\d+\.\d+).*
describeTagFirstParent: false
project version: 1.15.0-SNAPSHOT
> Task :spotlessInternalRegisterDependencies UP-TO-DATE
> Task :fineract-current-product:processResources NO-SOURCE
> Task :fineract-mix:processResources NO-SOURCE
> Task :fineract-rates:processResources UP-TO-DATE
> Task :fineract-core:processResources UP-TO-DATE
> Task :fineract-cob:processResources UP-TO-DATE
> Task :fineract-branch:processResources UP-TO-DATE
> Task :fineract-security:processResources NO-SOURCE
> Task :fineract-loan-origination:processResources UP-TO-DATE
> Task :fineract-savings:processResources UP-TO-DATE
> Task :fineract-accounting:processResources UP-TO-DATE
> Task :fineract-report:processResources UP-TO-DATE
> Task :fineract-charge:processResources UP-TO-DATE
> Task :fineract-investor:processResources UP-TO-DATE
> Task :fineract-loan:processResources UP-TO-DATE
> Task :fineract-client:processResources UP-TO-DATE
> Task :fineract-progressive-loan:processResources UP-TO-DATE
> Task :fineract-working-capital-loan:processResources UP-TO-DATE
> Task :fineract-document:processResources NO-SOURCE
> Task :fineract-tax:processResources UP-TO-DATE
> Task :fineract-avro-schemas:preprocessAvroSchemas UP-TO-DATE
> Task :fineract-validation:compileJava UP-TO-DATE
> Task :fineract-command:compileJava UP-TO-DATE
> Task :fineract-validation:processResources UP-TO-DATE
> Task :fineract-validation:classes UP-TO-DATE
> Task :fineract-command:processResources UP-TO-DATE
> Task :fineract-command:classes UP-TO-DATE
> Task :fineract-command:jar UP-TO-DATE
> Task :fineract-validation:jar UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJson UP-TO-DATE
> Task :fineract-avro-schemas:spotlessJsonApply UP-TO-DATE
> Task :fineract-avro-schemas:buildJavaSdk UP-TO-DATE
> Task :fineract-avro-schemas:compileJava UP-TO-DATE
> Task :fineract-avro-schemas:processResources UP-TO-DATE
> Task :fineract-avro-schemas:classes UP-TO-DATE
> Task :fineract-avro-schemas:jar UP-TO-DATE
> Task :fineract-working-capital-loan:compileJava UP-TO-DATE
> Task :fineract-working-capital-loan:classes UP-TO-DATE
> Task :fineract-working-capital-loan:jar UP-TO-DATE
> Task :fineract-core:compileJava UP-TO-DATE
> Task :fineract-core:classes UP-TO-DATE
> Task :fineract-core:jar UP-TO-DATE
> Task :fineract-mix:compileJava UP-TO-DATE
> Task :fineract-tax:compileJava UP-TO-DATE
> Task :fineract-mix:classes UP-TO-DATE
> Task :fineract-report:compileJava UP-TO-DATE
> Task :fineract-tax:classes UP-TO-DATE
> Task :fineract-mix:jar UP-TO-DATE
> Task :fineract-rates:compileJava UP-TO-DATE
> Task :fineract-report:classes UP-TO-DATE
> Task :fineract-cob:compileJava UP-TO-DATE
> Task :fineract-rates:classes UP-TO-DATE
> Task :fineract-cob:classes UP-TO-DATE
> Task :fineract-tax:jar UP-TO-DATE
> Task :fineract-report:jar UP-TO-DATE
> Task :fineract-cob:jar UP-TO-DATE
> Task :fineract-rates:jar UP-TO-DATE
> Task :fineract-document:compileJava UP-TO-DATE
> Task :fineract-document:classes UP-TO-DATE
> Task :fineract-security:compileJava UP-TO-DATE
> Task :fineract-document:jar UP-TO-DATE
> Task :fineract-security:classes UP-TO-DATE
> Task :fineract-security:jar UP-TO-DATE
> Task :fineract-charge:compileJava UP-TO-DATE
> Task :fineract-charge:classes UP-TO-DATE
> Task :fineract-charge:jar UP-TO-DATE
> Task :fineract-accounting:compileJava UP-TO-DATE
> Task :fineract-accounting:classes UP-TO-DATE
> Task :fineract-accounting:jar UP-TO-DATE
> Task :fineract-branch:compileJava UP-TO-DATE
> Task :fineract-branch:classes UP-TO-DATE
> Task :fineract-loan:compileJava UP-TO-DATE
> Task :fineract-loan:classes UP-TO-DATE
> Task :fineract-branch:jar UP-TO-DATE
> Task :fineract-loan:jar UP-TO-DATE
> Task :fineract-savings:compileJava UP-TO-DATE
> Task :fineract-savings:classes UP-TO-DATE
> Task :fineract-savings:jar UP-TO-DATE
> Task :fineract-loan-origination:compileJava UP-TO-DATE
> Task :fineract-investor:compileJava UP-TO-DATE
> Task :fineract-progressive-loan:compileJava UP-TO-DATE
> Task :fineract-loan-origination:classes UP-TO-DATE
> Task :fineract-progressive-loan:classes UP-TO-DATE
> Task :fineract-investor:classes UP-TO-DATE
> Task :fineract-progressive-loan:jar UP-TO-DATE
> Task :fineract-loan-origination:jar UP-TO-DATE
> Task :fineract-investor:jar UP-TO-DATE
> Task :fineract-current-product:compileJava UP-TO-DATE
> Task :fineract-current-product:classes UP-TO-DATE
> Task :fineract-current-product:jar UP-TO-DATE
> Task :fineract-provider:generateGitProperties
> Task :fineract-provider:compileJava UP-TO-DATE
> Task :fineract-provider:generateRebelMain UP-TO-DATE
> Task :fineract-provider:processResources
> Task :fineract-provider:classes
> Task :fineract-provider:compileTestJava UP-TO-DATE
> Task :fineract-provider:prepareInputYaml UP-TO-DATE
> Task :fineract-provider:processTestResources UP-TO-DATE
> Task :fineract-provider:testClasses UP-TO-DATE
> Task :fineract-provider:resolve
> Task :fineract-provider:resolveMainClassName
> Task :fineract-provider:bootRun
19:17:05,132 |-WARN in ch.qos.logback.core.joran.action.ConversionRuleAction - [converterClass] attribute is deprecated and replaced by [class]. See element [conversionRule] near line 30
_ _ _____ _ _
/ \ _ __ __ _ ___| |__ ___ | ___(_)_ __ ___ _ __ __ _ ___| |_
/ _ \ | '_ \ / _` |/ __| '_ \ / _ \ | |_ | | '_ \ / _ \ '__/ _` |/ __| __|
/ ___ \| |_) | (_| | (__| | | | __/ | _| | | | | | __/ | | (_| | (__| |_
/_/ \_\ .__/ \__,_|\___|_| |_|\___| |_| |_|_| |_|\___|_| \__,_|\___|\__|
|_|
(c) 2015-2024 Apache Fineract (https://fineract.apache.org)
Powered by Spring Boot 3.5.6
2026-05-29 19:17:05.618 - INFO 7760  [ no-tenant] --- [ main] org.apache.fineract.ServerApplication  : Starting ServerApplication using Java 21.0.9 with PID 7760 (C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main started by adity in C:\Users\adity\Music\CBS\fineract\fineract-provider)
2026-05-29 19:17:05.622 - INFO 7760  [ no-tenant] --- [ main] org.apache.fineract.ServerApplication  : No active profile set, falling back to 1 default profile: "default"
2026-05-29 19:17:10.836 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2026-05-29 19:17:10.836 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2026-05-29 19:17:11.794 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.documentmanagement.domain.DocumentRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:11.797 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.documentmanagement.domain.ImageRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:11.811 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixReportXBRLNamespaceRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:11.813 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixTaxonomyMappingRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:11.815 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.mix.domain.MixTaxonomyRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:11.887 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JPA - Could not safely identify store assignment for repository candidate interface org.apache.fineract.command.persistence.domain.CommandRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: jakarta.persistence.Entity, jakarta.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
2026-05-29 19:17:13.120 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 2267 ms. Found 224 JPA repository interfaces.
2026-05-29 19:17:13.130 - INFO 7760  [ no-tenant] --- [ main] o.s.b.f.s.DefaultListableBeanFactory  : Overriding bean definition for bean 'jpaAuditingHandler' with a different definition: replacing [Root bean: class=org.springframework.data.auditing.AuditingHandler; scope=; abstract=false; lazyInit=null; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=null; factoryMethodName=from; initMethodNames=null; destroyMethodNames=null] with [Root bean: class=org.apache.fineract.infrastructure.core.auditing.CustomAuditingHandler; scope=prototype; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null]
2026-05-29 19:17:13.204 - INFO 7760  [ no-tenant] --- [ main] o.s.b.f.s.DefaultListableBeanFactory  : Overriding bean definition for bean 'transactionManager' with a different definition: replacing [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=JPAConfig; factoryMethodName=transactionManager; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [org/apache/fineract/infrastructure/core/config/jpa/JPAConfig.class]] with [Root bean: class=null; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; fallback=false; factoryBeanName=scheduledJobRunnerConfig; factoryMethodName=transactionManager; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in class path resource [org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.class]]
2026-05-29 19:17:13.206 - INFO 7760  [ no-tenant] --- [ main] o.s.b.c.c.annotation.BatchRegistrar  : Bean jobRepository already defined in the application context, skipping the registration of a jobRepository
2026-05-29 19:17:13.206 - INFO 7760  [ no-tenant] --- [ main] o.s.b.c.c.annotation.BatchRegistrar  : Bean jobExplorer already defined in the application context, skipping the registration of a jobExplorer
2026-05-29 19:17:13.206 - INFO 7760  [ no-tenant] --- [ main] o.s.b.c.c.annotation.BatchRegistrar  : Bean jobLauncher already defined in the application context, skipping the registration of a jobLauncher
2026-05-29 19:17:13.208 - INFO 7760  [ no-tenant] --- [ main] o.s.b.c.c.annotation.BatchRegistrar  : Finished Spring Batch infrastructure beans configuration in 3 ms.
2026-05-29 19:17:14.107 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode
2026-05-29 19:17:14.108 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2026-05-29 19:17:14.408 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.provisioning.domain.ProvisioningEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.409 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.adhocquery.domain.AdHocRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.409 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.cob.domain.LoanAccountLockRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.bulkimport.domain.ImportDocumentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailCampaignRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.email.domain.EmailMessageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.410 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.configuration.domain.ExternalServicesPropertiesRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.412 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.412 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauLoanProductMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.412 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditBureauRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.413 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.CreditReportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.413 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.OrganisationCreditBureauRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.413 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.creditbureau.domain.TokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.414 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.EntityDatatableChecksRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.414 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.RegisteredDatatableRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.414 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.414 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportParameterUsageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.414 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.dataqueries.domain.ReportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.415 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityAccessRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.415 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.415 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.415 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.415 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.416 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.hooks.domain.HookTemplateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.416 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.imps.domain.ImpsTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.416 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.416 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.417 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.417 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.domain.SchedulerDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.417 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntryAggregationTrackingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.417 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.jobs.service.aggregationjob.domain.JournalEntrySummaryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.417 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.419 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.419 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.reportmailingjob.domain.ReportMailingJobRunHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.420 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.421 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.sms.domain.SmsMessageRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.421 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.survey.domain.LikelihoodRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.421 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.interoperation.domain.InteropIdentifierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.421 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.notification.domain.NotificationMapperRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.422 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.notification.domain.NotificationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.422 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.holiday.domain.HolidayRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.422 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.office.domain.OfficeTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.423 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCategoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.423 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaDefinitionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.423 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.provisioning.domain.ProvisioningCriteriaRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.424 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.staff.domain.StaffRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.424 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.424 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.425 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountTransferDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.425 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.AccountTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.426 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.account.domain.StandingInstructionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.426 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.address.domain.AddressRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.426 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.address.domain.FieldConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.calendar.domain.CalendarRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.ckyc.domain.CkycBatchRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientAddressRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.427 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.428 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientFamilyMembersRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.428 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientNonPersonRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.428 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.428 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientTransferDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.429 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagementRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.430 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomainRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.430 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.collateral.domain.LoanCollateralRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.431 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.BankIfscRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.431 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.CustomerTransferLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.431 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.FundTransferEventRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.FundTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.TransferLimitConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fundtransfer.domain.VerificationTokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.fund.domain.FundRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupLevelRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.432 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.433 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.group.domain.GroupRoleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.433 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.433 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorFundingTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.433 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.guarantor.domain.GuarantorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.433 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanproduct.productmix.domain.ProductMixRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.meeting.attendance.domain.ClientAttendanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.meeting.domain.MeetingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.note.domain.NoteRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.rate.domain.RateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.DepositAccountOnHoldTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.434 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.435 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.438 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FixedDepositAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.438 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FixedDepositProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.438 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdInterestRateEffectiveRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RdInterestRateSlabHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RecurringDepositAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPTRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.device.domain.DeviceRegistrationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.439 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.440 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.pockets.domain.PocketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.440 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistrationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.440 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountDividendRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.440 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareaccounts.domain.ShareAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.440 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareproducts.domain.ShareProductDividentPayOutDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.441 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.shareproducts.domain.ShareProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.441 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.LookupTableRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.442 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.ScorecardRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.444 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.spm.domain.SurveyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.444 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.template.domain.TemplateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.445 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.AppUserPreviousPasswordRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.445 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.AppUserRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.446 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.PasswordValidationPolicyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.446 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.PermissionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.446 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.domain.RoleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.446 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerJournalEntryMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.446 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerLoanProductAttributesRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.447 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.447 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferJournalEntryMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.447 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferLoanMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.447 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.investor.domain.ExternalAssetOwnerTransferRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.448 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.448 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanorigination.domain.LoanOriginatorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.448 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.449 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.449 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.449 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanAccrualActivityRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanApprovedAmountHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.450 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidByRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagementRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.452 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanStatusChangeHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.453 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.453 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.453 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.453 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.453 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.454 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.454 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.454 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.454 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.455 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.FacilityChargeMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.GSIMRepositoy; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.RecurringDepositProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.456 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfigRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.savings.domain.SavingsProductRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.cob.domain.BatchBusinessStepRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.CashierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.457 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.CashierTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.TellerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.teller.domain.TellerTransactionRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.closure.domain.GLClosureRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.glaccount.domain.TrialBalanceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.458 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.459 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.459 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.rule.domain.AccountingRuleRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.459 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.459 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.charge.domain.ChargeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.462 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.tax.domain.TaxComponentRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.462 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.tax.domain.TaxGroupRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.462 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.domain.TFAccessTokenRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.462 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.security.domain.TwoFactorConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.464 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.accounting.glaccount.domain.GLAccountRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.commands.domain.CommandSourceRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.cache.domain.PlatformCacheRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.codes.domain.CodeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.codes.domain.CodeValueRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.466 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.infrastructure.rbifeed.domain.RbiFeedItemRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.monetary.domain.OrganisationCurrencyRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.organisation.office.domain.OfficeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.CfcfrmsSimulatorLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.467 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.ComplaintAuditLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.468 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.cfcfrms.domain.ComplaintTicketRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.468 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientForm60Repository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.470 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientIdentifierRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.470 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientKycRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientNomineeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.domain.ClientRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientEouRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientImportExportRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientMsmeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.471 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientMudraBorrowerRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientNonPrioritySectorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientPrioritySectorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.client.entityclassification.domain.ClientStartupIndiaRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.mobilelookup.domain.MobileLookupLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.472 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.sanctions.domain.SanctionsEntryRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.473 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.portfolio.sanctions.domain.SanctionsScreeningLogRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.473 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.dsc.domain.BankUserDscRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.473 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.dsc.domain.DscChallengeRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.473 - INFO 7760  [ no-tenant] --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface org.apache.fineract.useradministration.mfa.domain.UserMfaFactorRepository; If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2026-05-29 19:17:14.768 - INFO 7760  [ no-tenant] --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 657 ms. Found 6 JDBC repository interfaces.
2026-05-29 19:17:16.886 - INFO 7760  [ no-tenant] --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
2026-05-29 19:17:16.895 - INFO 7760  [ no-tenant] --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
2026-05-29 19:17:18.104 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'fineract-org.apache.fineract.infrastructure.core.config.FineractProperties' of type [org.apache.fineract.infrastructure.core.config.FineractProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.117 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'cacheConfig' of type [org.apache.fineract.infrastructure.core.config.cache.CacheConfig$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.335 - INFO 7760  [ no-tenant] --- [ main] org.reflections.Reflections  : Reflections took 7 ms to scan 1 urls, producing 0 keys and 0 values
2026-05-29 19:17:18.339 - WARN 7760  [ no-tenant] --- [ main] o.a.f.i.core.config.cache.CacheConfig  : The following cache configurations are defined but cache does not exists: [userTFAccessToken]
2026-05-29 19:17:18.343 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'ehCacheManager' of type [org.springframework.cache.jcache.JCacheCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.354 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'defaultCacheManager' of type [org.apache.fineract.infrastructure.core.config.cache.TransactionBoundCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.360 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'runtimeDelegatingCacheManager' of type [org.apache.fineract.infrastructure.cache.service.RuntimeDelegatingCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.364 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'platformCacheConfiguration' of type [org.apache.fineract.infrastructure.cache.PlatformCacheConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.407 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'samplingConfiguration' of type [org.apache.fineract.infrastructure.core.diagnostics.performance.sampling.core.SamplingConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:18.415 - WARN 7760  [ no-tenant] --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'samplingServiceFactory' of type [org.apache.fineract.infrastructure.core.diagnostics.performance.sampling.core.SamplingServiceFactory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). Is this bean getting eagerly injected/applied to a currently created BeanPostProcessor [samplingBeanPostProcessor]? Check the corresponding BeanPostProcessor declaration and its dependencies/advisors. If this bean does not have to be post-processed, declare it with ROLE_INFRASTRUCTURE.
2026-05-29 19:17:19.255 - INFO 7760  [ no-tenant] --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8443 (https)
2026-05-29 19:17:19.278 - INFO 7760  [ no-tenant] --- [ main] o.apache.catalina.core.StandardService  : Starting service [Tomcat]
2026-05-29 19:17:19.279 - INFO 7760  [ no-tenant] --- [ main] o.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/10.1.49]
2026-05-29 19:17:19.328 - INFO 7760  [ no-tenant] --- [ main] o.a.c.c.C.[.[.[/fineract-provider]  : Initializing Spring embedded WebApplicationContext
2026-05-29 19:17:19.329 - INFO 7760  [ no-tenant] --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 13673 ms
2026-05-29 19:17:19.577 - INFO 7760  [ no-tenant] --- [ main] com.zaxxer.hikari.HikariDataSource  : HikariPool-1 - Starting...
2026-05-29 19:17:19.860 - INFO 7760  [ no-tenant] --- [ main] com.zaxxer.hikari.pool.HikariPool  : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@5917b984
2026-05-29 19:17:19.861 - INFO 7760  [ no-tenant] --- [ main] com.zaxxer.hikari.HikariDataSource  : HikariPool-1 - Start completed.
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : Upgrading tenant store DB at localhost:5432
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.username: postgres
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.password: ****
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.parameters:
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.timezone: Asia/Kolkata
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.description: Default Demo Tenant
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.identifier: default
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - fineract.tenant.name: fineract_default
2026-05-29 19:17:20.050 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : - liquibase.analytics.enabled: false
2026-05-29 19:17:25.487 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : Tenant store upgrade finished
2026-05-29 19:17:25.487 - INFO 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : Upgrading all tenants
2026-05-29 19:17:25.501 - INFO 7760  [ default] --- [lTaskExecutor-1] a.f.i.c.s.m.TenantDatabaseUpgradeService : Upgrade for tenant default has started
2026-05-29 19:17:25.842 - INFO 7760  [ default] --- [lTaskExecutor-1] com.zaxxer.hikari.HikariDataSource  : HikariPool-2 - Starting...
2026-05-29 19:17:25.946 - INFO 7760  [ default] --- [lTaskExecutor-1] com.zaxxer.hikari.pool.HikariPool  : HikariPool-2 - Added connection org.postgresql.jdbc.PgConnection@625805c1
2026-05-29 19:17:25.946 - INFO 7760  [ default] --- [lTaskExecutor-1] com.zaxxer.hikari.HikariDataSource  : HikariPool-2 - Start completed.
2026-05-29 19:17:31.700 -ERROR 7760  [ default] --- [lTaskExecutor-1] liquibase.changelog  : ChangeSet db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract encountered an exception.
liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:520)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:85)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:188)
at liquibase.executor.AbstractExecutor.execute(AbstractExecutor.java:148)
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1198)
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:816)
at liquibase.changelog.visitor.UpdateVisitor.executeAcceptedChange(UpdateVisitor.java:127)
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:71)
at liquibase.changelog.ChangeLogIterator.lambda$run$0(ChangeLogIterator.java:133)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.changelog.ChangeLogIterator.lambda$run$1(ChangeLogIterator.java:122)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.Scope.child(Scope.java:282)
at liquibase.Scope.child(Scope.java:286)
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:91)
at liquibase.command.core.AbstractUpdateCommandStep.lambda$run$0(AbstractUpdateCommandStep.java:114)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.command.core.AbstractUpdateCommandStep.run(AbstractUpdateCommandStep.java:112)
at liquibase.command.core.UpdateCommandStep.run(UpdateCommandStep.java:100)
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:263)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:201)
at liquibase.command.CommandScope.execute(CommandScope.java:251)
at liquibase.Liquibase.lambda$update$0(Liquibase.java:216)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.Liquibase.runInScope(Liquibase.java:1366)
at liquibase.Liquibase.update(Liquibase.java:205)
at liquibase.Liquibase.update(Liquibase.java:188)
at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:327)
at liquibase.integration.spring.SpringLiquibase.lambda$afterPropertiesSet$0(SpringLiquibase.java:279)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:272)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:205)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.lambda$upgradeIndividualTenants$1(TenantDatabaseUpgradeService.java:146)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null).
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2736)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2421)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:525)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:435)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:357)
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:342)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:318)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:313)
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95)
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:512)
... 52 common frames omitted
2026-05-29 19:17:31.748 - INFO 7760  [ default] --- [lTaskExecutor-1] com.zaxxer.hikari.HikariDataSource  : HikariPool-2 - Shutdown initiated...
2026-05-29 19:17:31.750 - INFO 7760  [ default] --- [lTaskExecutor-1] com.zaxxer.hikari.HikariDataSource  : HikariPool-2 - Shutdown completed.
2026-05-29 19:17:31.752 -ERROR 7760  [ no-tenant] --- [ main] a.f.i.c.s.m.TenantDatabaseUpgradeService : Exception:
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Exception while upgrading tenant default
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenants(TenantDatabaseUpgradeService.java:155)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.afterPropertiesSet(TenantDatabaseUpgradeService.java:93)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1725)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1474)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1698)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansWithAnnotation(DefaultListableBeanFactory.java:796)
at org.springframework.context.support.AbstractApplicationContext.getBeansWithAnnotation(AbstractApplicationContext.java:1440)
at org.apache.fineract.infrastructure.core.jersey.JerseyConfig.setup(JerseyConfig.java:58)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1818)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:230)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:221)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:106)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:95)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:271)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:245)
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4452)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:770)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:201)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:410)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:868)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:436)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
at org.apache.fineract.ServerApplication.main(ServerApplication.java:57)
Caused by: java.lang.RuntimeException: Exception while upgrading tenant default
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:208)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.lambda$upgradeIndividualTenants$1(TenantDatabaseUpgradeService.java:146)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: liquibase.exception.LiquibaseException: liquibase.exception.CommandExecutionException: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:289)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenant(TenantDatabaseUpgradeService.java:205)
... 5 common frames omitted
Caused by: liquibase.exception.CommandExecutionException: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:310)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:201)
at liquibase.command.CommandScope.execute(CommandScope.java:251)
at liquibase.Liquibase.lambda$update$0(Liquibase.java:216)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.Liquibase.runInScope(Liquibase.java:1366)
at liquibase.Liquibase.update(Liquibase.java:205)
at liquibase.Liquibase.update(Liquibase.java:188)
at liquibase.integration.spring.SpringLiquibase.performUpdate(SpringLiquibase.java:327)
at liquibase.integration.spring.SpringLiquibase.lambda$afterPropertiesSet$0(SpringLiquibase.java:279)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.integration.spring.SpringLiquibase.afterPropertiesSet(SpringLiquibase.java:272)
... 6 common frames omitted
Caused by: liquibase.exception.LiquibaseException: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:155)
at liquibase.command.core.AbstractUpdateCommandStep.lambda$run$0(AbstractUpdateCommandStep.java:114)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.command.core.AbstractUpdateCommandStep.run(AbstractUpdateCommandStep.java:112)
at liquibase.command.core.UpdateCommandStep.run(UpdateCommandStep.java:100)
at liquibase.command.CommandScope.lambda$execute$6(CommandScope.java:263)
... 24 common frames omitted
Caused by: liquibase.exception.MigrationFailedException: Migration failed for changeset db/changelog/tenant/parts/0309_phase3_jobs_and_recall_permission.xml::0309_imps_deemed_reconcile_job::fineract:
Reason: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:873)
at liquibase.changelog.visitor.UpdateVisitor.executeAcceptedChange(UpdateVisitor.java:127)
at liquibase.changelog.visitor.UpdateVisitor.visit(UpdateVisitor.java:71)
at liquibase.changelog.ChangeLogIterator.lambda$run$0(ChangeLogIterator.java:133)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.changelog.ChangeLogIterator.lambda$run$1(ChangeLogIterator.java:122)
at liquibase.Scope.lambda$child$0(Scope.java:216)
at liquibase.Scope.child(Scope.java:225)
at liquibase.Scope.child(Scope.java:215)
at liquibase.Scope.child(Scope.java:194)
at liquibase.Scope.child(Scope.java:282)
at liquibase.Scope.child(Scope.java:286)
at liquibase.changelog.ChangeLogIterator.run(ChangeLogIterator.java:91)
... 32 common frames omitted
Caused by: liquibase.exception.DatabaseException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null). [Failed SQL: (0) INSERT INTO public.job (name, display_name, cron_expression, create_time, task_priority, group_name, previous_run_start_time, job_key, initializing_errorlog, is_active, currently_running, updates_allowed, scheduler_group, is_misfired, node_id, is_mismatched_job) VALUES ('IMPS DEEMED Reconciliation', 'IMPS DEEMED Reconciliation', '0/30 * * * * ?', NOW(), 5, NULL, NULL, 'IMPS DEEMED ReconciliationDetail1 _ DEFAULT', NULL, FALSE, FALSE, TRUE, 0, FALSE, 1, TRUE)]
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:520)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:85)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:188)
at liquibase.executor.AbstractExecutor.execute(AbstractExecutor.java:148)
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1198)
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:816)
... 47 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "short_name" of relation "job" violates not-null constraint
Detail: Failing row contains (45, IMPS DEEMED Reconciliation, IMPS DEEMED Reconciliation, 0/30 * * * * ?, 2026-05-29 19:17:31.688453, 5, null, null, null, IMPS DEEMED ReconciliationDetail1 _ DEFAULT, null, f, f, t, 0, f, 1, t, null).
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2736)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2421)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:525)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:435)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:357)
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:342)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:318)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:313)
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95)
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java)
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:512)
... 52 common frames omitted
2026-05-29 19:17:31.761 -ERROR 7760  [ no-tenant] --- [ main] o.s.b.web.embedded.tomcat.TomcatStarter  : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'jerseyConfig': Invocation of init method failed
2026-05-29 19:17:31.812 - INFO 7760  [ no-tenant] --- [ main] o.apache.catalina.core.StandardService  : Stopping service [Tomcat]
2026-05-29 19:17:31.817 - WARN 7760  [ no-tenant] --- [ main] o.a.c.loader.WebappClassLoaderBase  : The web application [fineract-provider] appears to have started a thread named [HikariPool-1:housekeeper] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.base/jdk.internal.misc.Unsafe.park(Native Method)
java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)
java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1797)
java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)
java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)
java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
java.base/java.lang.Thread.run(Thread.java:1583)
2026-05-29 19:17:31.820 - WARN 7760  [ no-tenant] --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
2026-05-29 19:17:31.820 - INFO 7760  [ no-tenant] --- [ main] com.zaxxer.hikari.HikariDataSource  : HikariPool-1 - Shutdown initiated...
2026-05-29 19:17:31.822 - INFO 7760  [ no-tenant] --- [ main] com.zaxxer.hikari.HikariDataSource  : HikariPool-1 - Shutdown completed.
2026-05-29 19:17:31.847 - INFO 7760  [ no-tenant] --- [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2026-05-29 19:17:31.875 -ERROR 7760  [ no-tenant] --- [ main] o.s.boot.SpringApplication  : Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:170)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149)
at org.apache.fineract.ServerApplication.main(ServerApplication.java:57)
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:147)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:107)
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:517)
at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:219)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:193)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:167)
... 7 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 1: Error creating bean with name 'jerseyConfig': Invocation of init method failed
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:413)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:230)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:221)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:106)
at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:95)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:271)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:245)
at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:52)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4452)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:770)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1201)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1191)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:81)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:747)
at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:201)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.StandardService.startInternal(StandardService.java:410)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:868)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164)
at org.apache.catalina.startup.Tomcat.start(Tomcat.java:436)
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:128)
... 12 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jerseyConfig': Invocation of init method failed
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:222)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1818)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 61 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'journalEntriesApiResource' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\accounting\journalentry\api\JournalEntriesApiResource.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'springSecurityPlatformSecurityContext' defined in URL [jar:file:/C:/Users/adity/Music/CBS/fineract/fineract-security/build/libs/fineract-security-1.15.0-SNAPSHOT.jar!/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansWithAnnotation(DefaultListableBeanFactory.java:796)
at org.springframework.context.support.AbstractApplicationContext.getBeansWithAnnotation(AbstractApplicationContext.java:1440)
at org.apache.fineract.infrastructure.core.jersey.JerseyConfig.setup(JerseyConfig.java:58)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401)
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219)
... 74 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springSecurityPlatformSecurityContext' defined in URL [jar:file:/C:/Users/adity/Music/CBS/fineract/fineract-security/build/libs/fineract-security-1.15.0-SNAPSHOT.jar!/org/apache/fineract/infrastructure/security/service/SpringSecurityPlatformSecurityContext.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 91 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'configurationDomainServiceJpa' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\configuration\domain\ConfigurationDomainServiceJpa.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1760)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 105 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'permissionRepository' defined in org.apache.fineract.useradministration.domain.PermissionRepository defined in @EnableJpaRepositories declared on JPAConfig: Cannot resolve reference to bean 'jpaSharedEM_entityManagerFactory' while setting bean property 'entityManager'
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1725)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1474)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1698)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1643)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 119 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaSharedEM_entityManagerFactory': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:377)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:135)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:691)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
... 132 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tenantDatabaseUpgradeService' defined in file [C:\Users\adity\Music\CBS\fineract\fineract-provider\build\classes\java\main\org\apache\fineract\infrastructure\core\service\migration\TenantDatabaseUpgradeService.class]: Tenant upgrades had exceptions
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:365)
... 144 common frames omitted
Caused by: java.lang.RuntimeException: Tenant upgrades had exceptions
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.upgradeIndividualTenants(TenantDatabaseUpgradeService.java:167)
at org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService.afterPropertiesSet(TenantDatabaseUpgradeService.java:93)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822)
... 153 common frames omitted
> Task :fineract-provider:bootRun FAILED
[Incubating] Problems report is available at: file:///C:/Users/adity/Music/CBS/fineract/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':fineract-provider:bootRun'.
> Process 'command 'C:\Program Files\Eclipse Adoptium\jdk-21.0.9.10-hotspot\bin\java.exe'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.14.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1m 30s
83 actionable tasks: 5 executed, 78 up-to-date
Resource usage collection shutdown not awaited
-18
View File
@@ -1,18 +0,0 @@
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy UP-TO-DATE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Configure project :
matching ref: COMMIT - d7821c7824067a96d31c4c031ea538fc5e4107fc
ref configuration: COMMIT - pattern: null
version: ${describe.tag.version.major}.${describe.tag.version.minor.next}.0-SNAPSHOT
describeTagPattern: .*(\d+\.\d+\.\d+).*
describeTagFirstParent: false
project version: 0.1.0-SNAPSHOT
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -101,6 +101,13 @@ public class CashierDepositRequestApiResource {
final String bankName = fromJsonHelper.extractStringNamed("bankName", root);
final String txnDate = fromJsonHelper.extractStringNamed("txnDate", root);
final String denominationData = fromJsonHelper.extractStringNamed("denominationData", root);
// DD + Cash + GL breakdown from Transaction Entry - null for ordinary deposit requests.
// Persisted so the Sponsor DD issuance screen can read the breakdown back instead of
// retyping face value/charges once this request is approved and selected as cash evidence.
final BigDecimal ddFaceValue = parseBigDecimal(fromJsonHelper.extractStringNamed("ddFaceValue", root));
final BigDecimal ddCharges = parseBigDecimal(fromJsonHelper.extractStringNamed("ddCharges", root));
final BigDecimal ddGstPercentage = parseBigDecimal(fromJsonHelper.extractStringNamed("ddGstPercentage", root));
final BigDecimal ddGstAmount = parseBigDecimal(fromJsonHelper.extractStringNamed("ddGstAmount", root));
if (accountNo == null || accountNo.isBlank()) {
throw apiError(400, "Account number is required");
@@ -115,6 +122,7 @@ public class CashierDepositRequestApiResource {
throw apiError(400, "Amount must be greater than zero");
}
validateDenominationTotal(denominationData, amount);
validateDdLimit(ddFaceValue);
final String activityLog = buildActivityLog(null, "SUBMITTED", currentUser.getUsername(), null);
@@ -123,8 +131,9 @@ public class CashierDepositRequestApiResource {
(account_no, account_type, savings_account_id, gl_account_id, contra_gl_id, office_id, currency_code,
client_id, client_name, amount,
payment_type_id, payment_type_name, description, cheque_no, bank_name, txn_date,
status, cashier_id, cashier_name, cashier_username, denomination_data, activity_log, submitted_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
status, cashier_id, cashier_name, cashier_username, denomination_data, activity_log, submitted_at,
dd_face_value, dd_charges, dd_gst_percentage, dd_gst_amount)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
RETURNING id
""",
Long.class,
@@ -135,7 +144,8 @@ public class CashierDepositRequestApiResource {
txnDate != null ? Date.valueOf(txnDate) : null,
"PENDING",
currentUser.getId(), currentUser.getDisplayName(), currentUser.getUsername(),
denominationData, activityLog, Timestamp.from(Instant.now())
denominationData, activityLog, Timestamp.from(Instant.now()),
ddFaceValue, ddCharges, ddGstPercentage, ddGstAmount
);
// Globally unique voucher number from the central m_voucher register
@@ -189,7 +199,8 @@ public class CashierDepositRequestApiResource {
+ " amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,"
+ " txn_date, status, cashier_id, cashier_name, cashier_username,"
+ " manager_id, manager_name, manager_username, rejection_reason, txn_id,"
+ " denomination_data, activity_log, submitted_at, actioned_at"
+ " denomination_data, activity_log, submitted_at, actioned_at,"
+ " dd_face_value, dd_charges, dd_gst_percentage, dd_gst_amount"
+ " FROM m_cashier_deposit_request " + where
+ " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
pageParams.toArray());
@@ -242,7 +253,8 @@ public class CashierDepositRequestApiResource {
SELECT id, voucher_no, account_no, account_type, client_name, amount,
payment_type_name, description, txn_date, status,
cashier_name, manager_name, manager_username, rejection_reason, txn_id,
submitted_at, actioned_at
submitted_at, actioned_at,
dd_face_value, dd_charges, dd_gst_percentage, dd_gst_amount
FROM m_cashier_deposit_request
""" + where + " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
pageParams.toArray()
@@ -424,6 +436,7 @@ public class CashierDepositRequestApiResource {
final JsonObject res = new JsonObject();
res.addProperty("resourceId", id);
res.addProperty("status", "APPROVED");
res.addProperty("voucherNo", Optional.ofNullable(record.get("voucher_no")).map(Object::toString).orElse(""));
if (txnId != null) res.addProperty("txnId", txnId);
return gson.toJson(res);
}
@@ -532,7 +545,8 @@ public class CashierDepositRequestApiResource {
amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,
txn_date, status, cashier_id, cashier_name, cashier_username,
manager_id, manager_name, manager_username, rejection_reason, txn_id,
denomination_data, activity_log, submitted_at, actioned_at
denomination_data, activity_log, submitted_at, actioned_at,
dd_face_value, dd_charges, dd_gst_percentage, dd_gst_amount
FROM m_cashier_deposit_request WHERE id = ?
""", id);
if (rows.isEmpty()) {
@@ -869,6 +883,26 @@ public class CashierDepositRequestApiResource {
}
}
/**
* DD + Cash + GL deposit requests carry a Face Value (ddFaceValue) - reject the cash intake
* itself, before the cashier even accepts the money, if it exceeds the admin-configured DD
* limit (m_sponsor_dd_gl_mapping.dd_limit_amount). The same limit is enforced again later at
* actual DD issuance (sponsordd module); this is a first line of defense so an over-limit
* amount never even gets accepted as cash. ddFaceValue is null for ordinary (non-DD) deposit
* requests, which this check skips entirely. Null/zero-or-negative limit means unconfigured.
*/
private void validateDdLimit(final BigDecimal ddFaceValue) {
if (ddFaceValue == null) return;
final List<Map<String, Object>> rows = jdbcTemplate.queryForList(
"SELECT dd_limit_amount FROM m_sponsor_dd_gl_mapping LIMIT 1");
if (rows.isEmpty()) return;
final BigDecimal ddLimitAmount = (BigDecimal) rows.get(0).get("dd_limit_amount");
if (ddLimitAmount == null || ddLimitAmount.compareTo(BigDecimal.ZERO) <= 0) return;
if (ddFaceValue.compareTo(ddLimitAmount) > 0) {
throw apiError(400, "DD Face Value ₹" + ddFaceValue + " exceeds the configured DD limit of ₹" + ddLimitAmount + ".");
}
}
/**
* Checker authorization: only manager-level users may approve or reject.
* Allowed: super users (ALL_FUNCTIONS), or any role whose name contains
@@ -0,0 +1,84 @@
/**
* 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.customersearch.api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
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.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.customersearch.data.CustomerSearchResultData;
import org.apache.fineract.portfolio.customersearch.service.CustomerSearchReadPlatformService;
import org.springframework.stereotype.Component;
@Path("/v1/customer-search")
@Component
@Tag(name = "Customer Search", description = "Multi-field customer lookup by name, account number (savings/current/FD/RD), Aadhaar, PAN, and/or address.")
@RequiredArgsConstructor
public class CustomerSearchApiResource {
private final CustomerSearchReadPlatformService readPlatformService;
private final Gson gson = new GsonBuilder().serializeNulls().create();
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Search for a customer", description = "AND-combines whichever of name/accountNumber/aadhaarNumber/panNumber/address/accountTypes are supplied - at least one is required. accountTypes is a comma-separated list (SAVINGS,CURRENT,FIXED_DEPOSIT,RECURRING_DEPOSIT).")
public Collection<CustomerSearchResultData> search(@QueryParam("name") final String name,
@QueryParam("accountNumber") final String accountNumber, @QueryParam("aadhaarNumber") final String aadhaarNumber,
@QueryParam("panNumber") final String panNumber, @QueryParam("address") final String address,
@QueryParam("accountTypes") final String accountTypes) {
final List<String> accountTypeList = accountTypes == null || accountTypes.isBlank() ? List.of()
: Arrays.asList(accountTypes.split(","));
return safely(() -> this.readPlatformService.search(name, accountNumber, aadhaarNumber, panNumber, address, accountTypeList));
}
/** Surfaces IllegalArgumentException as a clean 400 instead of an opaque 500. */
private <T> T safely(final Supplier<T> action) {
try {
return action.get();
} catch (final IllegalArgumentException e) {
final JsonObject body = new JsonObject();
body.addProperty("message", e.getMessage());
body.addProperty("defaultUserMessage", e.getMessage());
body.addProperty("httpStatus", 400);
final JsonArray errors = new JsonArray();
final JsonObject err = new JsonObject();
err.addProperty("defaultUserMessage", e.getMessage());
errors.add(err);
body.add("errors", errors);
throw new WebApplicationException(
Response.status(400).entity(this.gson.toJson(body)).type(MediaType.APPLICATION_JSON).build());
}
}
}
@@ -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.portfolio.customersearch.data;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* One matching (client, account) combination from a multi-field customer search. A client with
* several accounts appears once per matching account; a client with no accounts at all still
* appears once, with accountNo/accountType left null.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomerSearchResultData implements Serializable {
private static final long serialVersionUID = 1L;
private Long clientId;
private String displayName;
private String mobileNo;
private String officeName;
private String accountNo;
/** SAVINGS, CURRENT, FIXED_DEPOSIT, or RECURRING_DEPOSIT. */
private String accountType;
private String panNumber;
private String aadhaarNumber;
private String address;
}
@@ -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.portfolio.customersearch.service;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.portfolio.customersearch.data.CustomerSearchResultData;
public interface CustomerSearchReadPlatformService {
/**
* Multi-field AND search - only non-blank parameters are applied. At least one must be
* supplied. name/address are partial, case-insensitive matches; accountNumber/aadhaarNumber/
* panNumber are exact matches (they're precise identifiers, not free text). accountTypes, if
* non-empty, restricts results to those account types only (SAVINGS/CURRENT/FIXED_DEPOSIT/
* RECURRING_DEPOSIT) - a client with no account, or only accounts of other types, is excluded
* entirely when this filter is active (unlike the unfiltered case, where a client with no
* account at all still appears once for informational purposes).
*/
Collection<CustomerSearchResultData> search(String name, String accountNumber, String aadhaarNumber, String panNumber,
String address, List<String> accountTypes);
}
@@ -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.portfolio.customersearch.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.customersearch.data.CustomerSearchResultData;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomerSearchReadPlatformServiceImpl implements CustomerSearchReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private static final Set<String> VALID_ACCOUNT_TYPES = Set.of("SAVINGS", "CURRENT", "FIXED_DEPOSIT", "RECURRING_DEPOSIT");
private static final String BASE_SELECT = """
SELECT DISTINCT c.id AS client_id, c.display_name, c.mobile_no, o.name AS office_name,
acct.account_no, acct.account_type,
pan.document_key AS pan_number, aad.document_key AS aadhaar_number,
addr.address_line_1, addr.address_line_2, addr.town_village, addr.city,
addr.county_district, addr.postal_code
FROM m_client c
LEFT JOIN m_office o ON o.id = c.office_id
LEFT JOIN m_client_identifier pan ON pan.client_id = c.id AND pan.document_type_id = (
SELECT id FROM m_code_value WHERE code_value = 'PAN Card'
AND code_id = (SELECT id FROM m_code WHERE code_name = 'KYCIdProofType'))
LEFT JOIN m_client_identifier aad ON aad.client_id = c.id AND aad.document_type_id = (
SELECT id FROM m_code_value WHERE code_value = 'Aadhaar Card'
AND code_id = (SELECT id FROM m_code WHERE code_name = 'KYCIdProofType'))
LEFT JOIN m_address addr ON addr.client_id = c.id
LEFT JOIN (
SELECT client_id, account_no,
CASE deposit_type_enum WHEN 100 THEN 'SAVINGS' WHEN 200 THEN 'FIXED_DEPOSIT'
WHEN 300 THEN 'RECURRING_DEPOSIT' ELSE 'SAVINGS' END AS account_type
FROM m_savings_account
UNION ALL
SELECT client_id, account_no, 'CURRENT' AS account_type FROM m_current_account
) acct ON acct.client_id = c.id
""";
@Override
public Collection<CustomerSearchResultData> search(final String name, final String accountNumber, final String aadhaarNumber,
final String panNumber, final String address, final List<String> accountTypes) {
final boolean hasName = name != null && !name.isBlank();
final boolean hasAccountNumber = accountNumber != null && !accountNumber.isBlank();
final boolean hasAadhaar = aadhaarNumber != null && !aadhaarNumber.isBlank();
final boolean hasPan = panNumber != null && !panNumber.isBlank();
final boolean hasAddress = address != null && !address.isBlank();
final List<String> effectiveAccountTypes = accountTypes == null ? List.of()
: accountTypes.stream().filter(t -> t != null && !t.isBlank()).map(String::trim).map(String::toUpperCase).toList();
if (!hasName && !hasAccountNumber && !hasAadhaar && !hasPan && !hasAddress && effectiveAccountTypes.isEmpty()) {
throw new IllegalArgumentException("Enter at least one search field - name, account number, Aadhaar, PAN, or address.");
}
for (final String type : effectiveAccountTypes) {
if (!VALID_ACCOUNT_TYPES.contains(type)) {
throw new IllegalArgumentException(
"Unknown account type: " + type + ". Valid values: SAVINGS, CURRENT, FIXED_DEPOSIT, RECURRING_DEPOSIT.");
}
}
final StringBuilder sql = new StringBuilder(BASE_SELECT).append(" WHERE 1=1 ");
final List<Object> params = new ArrayList<>();
if (hasName) {
sql.append(" AND LOWER(c.display_name) LIKE LOWER(?) ");
params.add("%" + name.trim() + "%");
}
if (hasAccountNumber) {
sql.append(" AND acct.account_no = ? ");
params.add(accountNumber.trim());
}
if (hasAadhaar) {
sql.append(" AND aad.document_key = ? ");
params.add(aadhaarNumber.trim());
}
if (hasPan) {
sql.append(" AND UPPER(pan.document_key) = UPPER(?) ");
params.add(panNumber.trim());
}
if (hasAddress) {
sql.append(" AND (LOWER(addr.address_line_1) LIKE LOWER(?) OR LOWER(addr.address_line_2) LIKE LOWER(?) "
+ "OR LOWER(addr.town_village) LIKE LOWER(?) OR LOWER(addr.city) LIKE LOWER(?) "
+ "OR LOWER(addr.county_district) LIKE LOWER(?) OR LOWER(addr.postal_code) LIKE LOWER(?)) ");
final String likeAddress = "%" + address.trim() + "%";
for (int i = 0; i < 6; i++) {
params.add(likeAddress);
}
}
if (!effectiveAccountTypes.isEmpty()) {
// Restricting to specific account types excludes both non-matching-type accounts AND
// clients with no account at all (acct.account_type would be NULL for those, which can
// never equal one of the listed types) - that's the point of the filter.
final String placeholders = String.join(",", effectiveAccountTypes.stream().map(t -> "?").toList());
sql.append(" AND acct.account_type IN (").append(placeholders).append(") ");
params.addAll(effectiveAccountTypes);
}
sql.append(" ORDER BY c.display_name ASC LIMIT 100");
return this.jdbcTemplate.query(sql.toString(), new CustomerSearchResultMapper(), params.toArray());
}
private static final class CustomerSearchResultMapper implements RowMapper<CustomerSearchResultData> {
@Override
public CustomerSearchResultData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long clientId = rs.getLong("client_id");
final String displayName = rs.getString("display_name");
final String mobileNo = rs.getString("mobile_no");
final String officeName = rs.getString("office_name");
final String accountNo = rs.getString("account_no");
final String accountType = rs.getString("account_type");
final String panNumber = rs.getString("pan_number");
final String aadhaarNumber = rs.getString("aadhaar_number");
final List<String> addressParts = new ArrayList<>();
addPart(addressParts, rs.getString("address_line_1"));
addPart(addressParts, rs.getString("address_line_2"));
addPart(addressParts, rs.getString("town_village"));
addPart(addressParts, rs.getString("city"));
addPart(addressParts, rs.getString("county_district"));
addPart(addressParts, rs.getString("postal_code"));
final String address = addressParts.isEmpty() ? null : String.join(", ", addressParts);
return new CustomerSearchResultData(clientId, displayName, mobileNo, officeName, accountNo, accountType, panNumber,
aadhaarNumber, address);
}
private void addPart(final List<String> parts, final String value) {
if (value != null && !value.isBlank()) {
parts.add(value);
}
}
}
}
@@ -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.portfolio.sponsordd.api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
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.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.function.Supplier;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBank;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBankRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.stereotype.Component;
@Path("/v1/sponsorbanks")
@Component
@Tag(name = "Sponsor Bank", description = "Endpoints for managing sponsor banks.")
@RequiredArgsConstructor
public class SponsorBankApiResource {
private final SponsorBankRepository sponsorBankRepository;
private final PlatformSecurityContext context;
private final Gson gson = new GsonBuilder().serializeNulls().create();
/** Mirrors SponsorDDApiResource.safely() - see that class for the full rationale. */
private <T> T safely(final Supplier<T> action) {
try {
return action.get();
} catch (final IllegalArgumentException | IllegalStateException e) {
final JsonObject body = new JsonObject();
body.addProperty("message", e.getMessage());
body.addProperty("defaultUserMessage", e.getMessage());
body.addProperty("httpStatus", 400);
final JsonArray errors = new JsonArray();
final JsonObject err = new JsonObject();
err.addProperty("defaultUserMessage", e.getMessage());
errors.add(err);
body.add("errors", errors);
throw new WebApplicationException(
Response.status(400).entity(this.gson.toJson(body)).type(MediaType.APPLICATION_JSON).build());
}
}
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve all Sponsor Banks", description = "Returns a list of all registered Sponsor Banks.")
public Collection<SponsorBankData> retrieveAllSponsorBanks() {
return this.sponsorBankRepository.findAll().stream()
.map(sb -> new SponsorBankData(sb.getId(), sb.getName(), sb.getDescription(), sb.getIfscCode(), sb.getBankCode(),
sb.getSwiftCode()))
.toList();
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a Sponsor Bank", description = "Creates a new Sponsor Bank.")
public CommandProcessingResult createSponsorBank(final SponsorBankRequest request) {
return safely(() -> {
if (request.getIfscCode() != null && !request.getIfscCode().isBlank()
&& !request.getIfscCode().trim().toUpperCase().matches("^[A-Z]{4}0[A-Z0-9]{6}$")) {
throw new IllegalArgumentException(
"IFSC Code must be 11 characters: 4 letters, then '0', then 6 alphanumeric characters (e.g. SBIN0001234).");
}
final AppUser currentUser = this.context.authenticatedUser();
final SponsorBank sponsorBank = new SponsorBank(
request.getName(),
request.getDescription(),
request.getIfscCode() != null ? request.getIfscCode().trim().toUpperCase() : null,
request.getBankCode(),
request.getSwiftCode() != null ? request.getSwiftCode().trim().toUpperCase() : null,
currentUser,
LocalDateTime.now()
);
this.sponsorBankRepository.saveAndFlush(sponsorBank);
return new CommandProcessingResultBuilder().withEntityId(sponsorBank.getId()).build();
});
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SponsorBankData {
private Long id;
private String name;
private String description;
private String ifscCode;
private String bankCode;
private String swiftCode;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SponsorBankRequest {
private String name;
private String description;
private String ifscCode;
private String bankCode;
private String swiftCode;
}
}
@@ -0,0 +1,633 @@
/**
* 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.sponsordd.api;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
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.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDBatchIssueResult;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDCashierDepositOptionData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDGlMappingData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDInventoryData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDIssueRequestData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDLeafletData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDSlipLine;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoBatch;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDReprintRequestData;
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDIssueRequestWritePlatformService;
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDReadPlatformService;
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDReconciliationService;
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDReprintRequestWritePlatformService;
import org.apache.fineract.portfolio.sponsordd.service.SponsorDDWritePlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.stereotype.Component;
@Path("/v1/sponsordd")
@Component
@Tag(name = "Sponsor Demand Draft", description = "Endpoints for managing sponsor bank demand draft inventory, leaflet lifecycles, and clearing reconciliation.")
@RequiredArgsConstructor
public class SponsorDDApiResource {
private final SponsorDDReadPlatformService readPlatformService;
private final SponsorDDWritePlatformService writePlatformService;
private final SponsorDDReconciliationService reconciliationService;
private final SponsorDDIssueRequestWritePlatformService issueRequestWritePlatformService;
private final SponsorDDReprintRequestWritePlatformService reprintRequestWritePlatformService;
private final PlatformSecurityContext context;
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final Gson gson = new GsonBuilder().serializeNulls().create();
/**
* Business-rule validation in this module is expressed with plain IllegalArgumentException /
* IllegalStateException (see SponsorDDWritePlatformServiceImpl / SponsorDDIssueRequestWritePlatformServiceImpl).
* Those aren't mapped by Fineract's global exception handler, so left uncaught they surface to the
* frontend as an opaque 500. This wraps every write endpoint so validation failures come back as a
* proper 400 with the actual message.
*/
private <T> T safely(final Supplier<T> action) {
try {
return action.get();
} catch (final IllegalArgumentException | IllegalStateException e) {
throw apiError(400, e.getMessage());
}
}
private WebApplicationException apiError(final int httpStatus, final String message) {
final JsonObject body = new JsonObject();
body.addProperty("message", message);
body.addProperty("defaultUserMessage", message);
body.addProperty("httpStatus", httpStatus);
final JsonArray errors = new JsonArray();
final JsonObject err = new JsonObject();
err.addProperty("defaultUserMessage", message);
errors.add(err);
body.add("errors", errors);
return new WebApplicationException(
Response.status(httpStatus).entity(this.gson.toJson(body)).type(MediaType.APPLICATION_JSON).build());
}
@GET
@Path("inventory")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve all DD Inventories", description = "Returns a list of all registered Sponsor DD Inventory blocks.")
public Collection<SponsorDDInventoryData> retrieveAllInventories() {
return this.readPlatformService.retrieveAllInventories();
}
@GET
@Path("inventory/{inventoryId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve DD Inventory block details", description = "Returns detail metadata for a specific DD inventory block.")
public SponsorDDInventoryData retrieveInventory(@PathParam("inventoryId") final Long inventoryId) {
return this.readPlatformService.retrieveInventory(inventoryId);
}
@POST
@Path("inventory")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Register new DD Inventory block", description = "Creates a new DD inventory series and auto-generates associated available leaflets.")
public CommandProcessingResult registerInventory(final RegisterInventoryRequest request) {
final LocalDate receivedDate = LocalDate.parse(request.getReceivedDate(), this.dateFormatter);
return safely(() -> this.writePlatformService.registerInventory(
request.getSponsorBankId(),
request.getBranchId(),
request.getSeriesStart(),
request.getSeriesEnd(),
receivedDate
));
}
@GET
@Path("inventory/{inventoryId}/leaflets")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve all leaflets in an inventory", description = "Returns status registry list of all leaflets under a specific inventory batch.")
public Collection<SponsorDDLeafletData> retrieveAllLeafletsByInventory(@PathParam("inventoryId") final Long inventoryId) {
return this.readPlatformService.retrieveAllLeafletsByInventory(inventoryId);
}
@GET
@Path("leaflets/{leafletId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve specific leaflet details", description = "Returns detailed metadata and audit properties for a single leaflet.")
public SponsorDDLeafletData retrieveLeaflet(@PathParam("leafletId") final Long leafletId) {
return this.readPlatformService.retrieveLeaflet(leafletId);
}
@GET
@Path("leaflets/by-number/{ddNumber}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Find leaflet by DD Number", description = "Finds and returns a leaflet's data structure searching by its pre-printed DD Number.")
public SponsorDDLeafletData retrieveLeafletByDdNumber(@PathParam("ddNumber") final String ddNumber) {
return this.readPlatformService.retrieveLeafletByDdNumber(ddNumber);
}
@POST
@Path("leaflets/{leafletId}/issue")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Issue a demand draft", description = "Transitions an AVAILABLE leaflet to ISSUED and links it to customer debit transactions.")
public CommandProcessingResult issueDemandDraft(@PathParam("leafletId") final Long leafletId, final IssueDDRequest request) {
final LocalDate issuedDate = LocalDate.parse(request.getIssuedDate(), this.dateFormatter);
return safely(() -> this.writePlatformService.issueDemandDraft(
leafletId,
request.getClientId(),
null,
null,
null,
request.getBeneficiaryName(),
request.getFaceValue(),
request.getIssuingBranchId(),
issuedDate,
request.getSavingsTransactionId(),
request.getCurrentTransactionId(),
request.getJournalEntryId(),
null,
null
));
}
@POST
@Path("leaflets/{leafletId}/cancel")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Cancel a demand draft", description = "Transitions an ISSUED leaflet to CANCELLED and maps customer refund details.")
public CommandProcessingResult cancelDemandDraft(@PathParam("leafletId") final Long leafletId, final CancelDDRequest request) {
return safely(() -> this.writePlatformService.cancelDemandDraft(
leafletId,
request.getRemarks(),
request.getRefundSavingsTransactionId(),
request.getRefundCurrentTransactionId()
));
}
@POST
@Path("leaflets/{leafletId}/stop")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Process Stop Payment instruction", description = "Transitions an ISSUED leaflet to STOPPED.")
public CommandProcessingResult stopPaymentDemandDraft(@PathParam("leafletId") final Long leafletId, final StopDDRequest request) {
return safely(() -> this.writePlatformService.stopPaymentDemandDraft(leafletId, request.getRemarks()));
}
@GET
@Path("leaflets")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve leaflets with filter", description = "Returns status registry list of leaflets filtered by sponsor bank, status, and advice status.")
public Collection<SponsorDDLeafletData> retrieveLeaflets(
@QueryParam("sponsorBankId") final Long sponsorBankId,
@QueryParam("status") final String status,
@QueryParam("adviceStatus") final String adviceStatus) {
return this.readPlatformService.retrieveLeaflets(sponsorBankId, status, adviceStatus);
}
@POST
@Path("leaflets/send-advice")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Send advice to sponsor bank", description = "Marks all issued, unsent leaflets for a specific sponsor bank as sent.")
public CommandProcessingResult sendAdvice(final SendAdviceRequest request) {
return safely(() -> this.writePlatformService.sendAdvice(request.getSponsorBankId()));
}
// ── Maker-checker DD issuance (clerk submits, manager/head-cashier approves) ──
@POST
@Path("leaflets/{leafletId}/issue-request")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Submit a DD issuance request", description = "Clerk (maker) step: records a pending DD issuance request funded either by an account debit or a cheque. No money moves until a manager approves it.")
public CommandProcessingResult submitIssueRequest(@PathParam("leafletId") final Long leafletId, final IssueRequestRequest request) {
final LocalDate issuedDate = LocalDate.parse(request.getIssuedDate(), this.dateFormatter);
return safely(() -> this.issueRequestWritePlatformService.submitIssueRequest(
leafletId,
request.getClientId(),
request.getWalkinPurchaserName(),
request.getWalkinPurchaserAddress(),
request.getWalkinPurchaserAadhaar(),
request.getBeneficiaryName(),
request.getPayableAtBranch(),
request.getPayableAtCity(),
request.getFaceValue(),
request.getChargesAmount(),
request.getIssuingBranchId(),
issuedDate,
request.getPaymentMethod(),
request.getSavingsAccountId(),
request.getCurrentAccountId(),
request.getChequeNumber(),
request.getChequeBankName(),
request.getCashierDepositRequestId()
));
}
@POST
@Path("inventory/{inventoryId}/issue-request-batch")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Submit a multi-DD slip", description = "Clerk (maker) step: one account debit or cheque funds several drafts (different beneficiaries/amounts, charges optional per line) submitted together. A physical leaflet is auto-allocated per line from this inventory - the response shows exactly which DD number and beneficiary/amount was assigned to each line. No money moves until a manager approves the whole slip.")
public SponsorDDBatchIssueResult submitIssueRequestBatch(@PathParam("inventoryId") final Long inventoryId,
final IssueRequestBatchRequest request) {
final LocalDate issuedDate = LocalDate.parse(request.getIssuedDate(), this.dateFormatter);
final List<SponsorDDSlipLine> lines = request.getLines() == null ? List.of()
: request.getLines().stream()
.map(l -> new SponsorDDSlipLine(l.getBeneficiaryName(), l.getPayableAtBranch(), l.getPayableAtCity(),
l.getFaceValue(), l.getChargesAmount()))
.collect(java.util.stream.Collectors.toList());
return safely(() -> this.issueRequestWritePlatformService.submitIssueRequestBatch(
inventoryId,
request.getClientId(),
request.getIssuingBranchId(),
issuedDate,
request.getPaymentMethod(),
request.getSavingsAccountId(),
request.getCurrentAccountId(),
request.getChequeNumber(),
request.getChequeBankName(),
lines
));
}
@POST
@Path("issue-requests/batch/{batchId}/approve")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Approve a multi-DD slip", description = "Manager/head-cashier (checker) step: one combined GL posting for the whole slip's total, then issues every draft in it. Approves every line together, not one at a time.")
public CommandProcessingResult approveIssueRequestBatch(@PathParam("batchId") final Long batchId) {
return safely(() -> this.issueRequestWritePlatformService.approveIssueRequestBatch(batchId));
}
@POST
@Path("issue-requests/batch/{batchId}/reject")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Reject a multi-DD slip", description = "Manager/head-cashier (checker) step: rejects every line of a pending slip together. All leaflets remain AVAILABLE.")
public CommandProcessingResult rejectIssueRequestBatch(@PathParam("batchId") final Long batchId, final RejectIssueRequestRequest request) {
return safely(() -> this.issueRequestWritePlatformService.rejectIssueRequestBatch(batchId, request.getRejectionReason()));
}
@GET
@Path("cashier-deposit-requests/available")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve available cashier deposit requests for CASH DD issuance", description = "Returns approved cashier Transaction Entry (AC.TP=GL) deposit requests credited to the DD Cash Suspense GL account, not yet linked to any DD issuance request.")
public Collection<SponsorDDCashierDepositOptionData> retrieveAvailableCashierDepositRequests() {
return this.readPlatformService.retrieveAvailableCashierDepositRequests();
}
@GET
@Path("issue-requests")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve DD issuance requests", description = "Returns all DD issuance requests, optionally filtered by status (PENDING/APPROVED/REJECTED), for the manager approval queue.")
public Collection<SponsorDDIssueRequestData> retrieveAllIssueRequests(@QueryParam("status") final String status) {
return this.readPlatformService.retrieveAllIssueRequests(status);
}
@GET
@Path("issue-requests/mine")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve my DD issuance requests", description = "Returns the DD issuance requests submitted by the current clerk.")
public Collection<SponsorDDIssueRequestData> retrieveMyIssueRequests() {
return this.readPlatformService.retrieveMyIssueRequests(this.context.authenticatedUser().getId());
}
@GET
@Path("issue-requests/{issueRequestId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve a DD issuance request", description = "Returns detail for a single DD issuance request.")
public SponsorDDIssueRequestData retrieveIssueRequest(@PathParam("issueRequestId") final Long issueRequestId) {
return this.readPlatformService.retrieveIssueRequest(issueRequestId);
}
@POST
@Path("issue-requests/{issueRequestId}/approve")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Approve a DD issuance request", description = "Manager/head-cashier (checker) step: performs the real GL posting (client account debit or cheque-clearing debit, credited to the Sponsor DD Payable GL account) and issues the demand draft.")
public CommandProcessingResult approveIssueRequest(@PathParam("issueRequestId") final Long issueRequestId) {
return safely(() -> this.issueRequestWritePlatformService.approveIssueRequest(issueRequestId));
}
@POST
@Path("issue-requests/{issueRequestId}/reject")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Reject a DD issuance request", description = "Manager/head-cashier (checker) step: rejects a pending DD issuance request. The leaflet remains AVAILABLE.")
public CommandProcessingResult rejectIssueRequest(@PathParam("issueRequestId") final Long issueRequestId, final RejectIssueRequestRequest request) {
return safely(() -> this.issueRequestWritePlatformService.rejectIssueRequest(issueRequestId, request.getRejectionReason()));
}
// ── DD reprint requests (maker-checker) ─────────────────────────────────
@POST
@Path("leaflets/{leafletId}/print")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Record a print of an issued DD", description = "First print of a leaflet is always allowed. Any print after that consumes a previously-approved reprint request, or fails if none exists.")
public CommandProcessingResult recordPrint(@PathParam("leafletId") final Long leafletId) {
return safely(() -> this.reprintRequestWritePlatformService.recordPrint(leafletId));
}
@POST
@Path("leaflets/{leafletId}/reprint-request")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Request permission to reprint a DD", description = "Clerk (maker) step: requests manager approval to print an already-printed leaflet again.")
public CommandProcessingResult requestReprint(@PathParam("leafletId") final Long leafletId, final ReprintRequestRequest request) {
return safely(() -> this.reprintRequestWritePlatformService.requestReprint(leafletId, request.getReason()));
}
@GET
@Path("reprint-requests")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve DD reprint requests", description = "Returns all DD reprint requests, optionally filtered by status (PENDING/APPROVED/REJECTED), for the manager approval queue.")
public Collection<SponsorDDReprintRequestData> retrieveAllReprintRequests(@QueryParam("status") final String status) {
return this.readPlatformService.retrieveAllReprintRequests(status);
}
@POST
@Path("reprint-requests/{reprintRequestId}/approve")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Approve a DD reprint request", description = "Manager/head-cashier (checker) step: allows one further print of the leaflet.")
public CommandProcessingResult approveReprintRequest(@PathParam("reprintRequestId") final Long reprintRequestId) {
return safely(() -> this.reprintRequestWritePlatformService.approveReprintRequest(reprintRequestId));
}
@POST
@Path("reprint-requests/{reprintRequestId}/reject")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Reject a DD reprint request", description = "Manager/head-cashier (checker) step: rejects a pending DD reprint request.")
public CommandProcessingResult rejectReprintRequest(@PathParam("reprintRequestId") final Long reprintRequestId, final RejectIssueRequestRequest request) {
return safely(() -> this.reprintRequestWritePlatformService.rejectReprintRequest(reprintRequestId, request.getRejectionReason()));
}
// ── Sponsor DD GL account configuration ───────────────────────────────
@GET
@Path("gl-mapping")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve Sponsor DD GL mappings", description = "Returns the configured GL accounts (DD Payable liability + cheque-clearing suspense) for each sponsor bank / payment type.")
public Collection<SponsorDDGlMappingData> retrieveAllGlMappings() {
return this.readPlatformService.retrieveAllGlMappings();
}
@POST
@Path("gl-mapping")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Configure a Sponsor DD GL mapping", description = "Creates or updates the DD Payable / cheque-clearing GL accounts for a sponsor bank / payment type.")
public CommandProcessingResult upsertGlMapping(final GlMappingRequest request) {
return safely(() -> this.issueRequestWritePlatformService.upsertGlMapping(
request.getSponsorBankId(),
request.getDdPayableGlAccountId(),
request.getChequeClearingGlAccountId(),
request.getCashGlAccountId(),
request.getChargesIncomeGlAccountId(),
request.getCashSuspenseGlAccountId(),
request.getGstPercentage(),
request.getGstPayableGlAccountId(),
request.getDdLimitAmount()));
}
@POST
@Path("reconcile/upload")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Upload and process clearing scroll", description = "Imports Sponsor Bank clearing scroll content, matching DD numbers and face values.")
public RecoBatchResponse uploadAndReconcile(final RecoUploadRequest request) {
return safely(() -> {
final SponsorDDRecoBatch batch = this.reconciliationService.uploadAndReconcile(request.getFileName(), request.getFileContent());
return new RecoBatchResponse(
batch.getId(),
batch.getBatchReference(),
batch.getStatus(),
batch.getTotalRecords(),
batch.getSuccessCount(),
batch.getMismatchCount()
);
});
}
@POST
@Path("reconcile/resolve/{recordId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Resolve matching exception", description = "Manually resolves a reconciliation mismatch by approving force-match matching.")
public CommandProcessingResult resolveMismatch(@PathParam("recordId") final Long recordId, final ResolveMismatchRequest request) {
return safely(() -> this.reconciliationService.resolveMismatch(
recordId,
request.getActionType(),
request.getResolvedSavingsTxnId(),
request.getResolvedCurrentTxnId()
));
}
@GET
@Path("reconcile/batches")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve all reconciliation batches", description = "Returns a list of all clearing scrolls processed.")
public Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData> retrieveAllReconciliationBatches() {
return this.readPlatformService.retrieveAllReconciliationBatches();
}
@GET
@Path("reconcile/batches/{batchId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve reconciliation batch detail", description = "Returns metadata for a specific reconciliation upload batch.")
public org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData retrieveReconciliationBatch(@PathParam("batchId") final Long batchId) {
return this.readPlatformService.retrieveReconciliationBatch(batchId);
}
@GET
@Path("reconcile/batches/{batchId}/records")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve records in a reconciliation batch", description = "Returns all matched and exception scroll records within a specific batch.")
public Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData> retrieveReconciliationRecordsByBatch(@PathParam("batchId") final Long batchId) {
return this.readPlatformService.retrieveReconciliationRecordsByBatch(batchId);
}
// Requests DTOs
@lombok.Data
public static class RegisterInventoryRequest {
private Long sponsorBankId;
private Long branchId;
private Long seriesStart;
private Long seriesEnd;
private String receivedDate;
}
@lombok.Data
public static class IssueDDRequest {
private Long clientId;
private String beneficiaryName;
private BigDecimal faceValue;
private Long issuingBranchId;
private String issuedDate;
private Long savingsTransactionId;
private Long currentTransactionId;
private Long journalEntryId;
}
@lombok.Data
public static class CancelDDRequest {
private String remarks;
private Long refundSavingsTransactionId;
private Long refundCurrentTransactionId;
}
@lombok.Data
public static class StopDDRequest {
private String remarks;
}
@lombok.Data
public static class RecoUploadRequest {
private String fileName;
private String fileContent;
}
@lombok.Data
public static class ResolveMismatchRequest {
private String actionType; // FORCE_MATCH
private Long resolvedSavingsTxnId;
private Long resolvedCurrentTxnId;
}
@lombok.Data
public static class IssueRequestRequest {
private Long clientId;
// Walk-in (non-client) purchaser details — required instead of clientId when paymentMethod is CASH.
private String walkinPurchaserName;
private String walkinPurchaserAddress;
private String walkinPurchaserAadhaar; // raw 12-digit Aadhaar — masked to last 4 digits before storage
private String beneficiaryName;
private String payableAtBranch;
private String payableAtCity;
private BigDecimal faceValue;
private BigDecimal chargesAmount; // DD issuance commission/charges, if any
private Long issuingBranchId;
private String issuedDate;
private String paymentMethod; // ACCOUNT_DEBIT, CHEQUE, or CASH
private Long savingsAccountId;
private Long currentAccountId;
private String chequeNumber;
private String chequeBankName;
// CASH only — the cashier's already-APPROVED Transaction Entry (AC.TP=GL) deposit request
// that received this cash, credited to the DD Cash Suspense GL account.
private Long cashierDepositRequestId;
}
@lombok.Data
public static class IssueRequestBatchRequest {
private Long clientId;
private Long issuingBranchId;
private String issuedDate;
private String paymentMethod; // ACCOUNT_DEBIT or CHEQUE only — a slip does not support CASH
private Long savingsAccountId;
private Long currentAccountId;
private String chequeNumber;
private String chequeBankName;
private List<SlipLineRequest> lines;
}
@lombok.Data
public static class SlipLineRequest {
private String beneficiaryName;
private String payableAtBranch;
private String payableAtCity;
private BigDecimal faceValue;
private BigDecimal chargesAmount;
}
@lombok.Data
public static class RejectIssueRequestRequest {
private String rejectionReason;
}
@lombok.Data
public static class ReprintRequestRequest {
private String reason;
}
@lombok.Data
public static class GlMappingRequest {
private Long sponsorBankId;
private Long ddPayableGlAccountId;
private Long chequeClearingGlAccountId;
private Long cashGlAccountId;
private Long chargesIncomeGlAccountId;
private Long cashSuspenseGlAccountId;
private BigDecimal gstPercentage;
private Long gstPayableGlAccountId;
private BigDecimal ddLimitAmount;
}
@lombok.Data
@lombok.AllArgsConstructor
public static class RecoBatchResponse {
private Long id;
private String batchReference;
private String status;
private Integer totalRecords;
private Integer successCount;
private Integer mismatchCount;
}
@lombok.Data
public static class SendAdviceRequest {
private Long sponsorBankId;
}
}
@@ -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.sponsordd.data;
import java.math.BigDecimal;
import java.util.List;
/**
* Response for submitIssueRequestBatch - tells the maker exactly which physical leaflet (DD number)
* was auto-allocated to each line and for what amount, since that allocation only happens inside the
* submit transaction (locked against concurrent slips) and can't be known beforehand.
*/
public record SponsorDDBatchIssueResult(Long batchId, List<Line> lines) {
public record Line(Long issueRequestId, Long leafletId, String ddNumber, String beneficiaryName, BigDecimal faceValue) {}
}
@@ -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.portfolio.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* A cashier's Transaction Entry (AC.TP=GL) deposit request, already approved, credited to the DD
* Cash Suspense GL account, and not yet linked to any Sponsor DD issue request - i.e. available to
* be selected as the cash-receipt evidence for a new CASH DD issuance request.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDCashierDepositOptionData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String voucherNo;
private BigDecimal amount;
private String description;
private String cashierUsername;
private LocalDateTime actionedAt;
// DD face value / charges / GST breakdown captured at Transaction Entry submit time - null for
// deposit requests that were not raised as a DD + Cash + GL row.
private BigDecimal ddFaceValue;
private BigDecimal ddCharges;
private BigDecimal ddGstPercentage;
private BigDecimal ddGstAmount;
}
@@ -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.portfolio.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDGlMappingData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long sponsorBankId;
private String sponsorBankName;
private Long ddPayableGlAccountId;
private String ddPayableGlAccountName;
private Long chequeClearingGlAccountId;
private String chequeClearingGlAccountName;
private Long cashGlAccountId;
private String cashGlAccountName;
private Long chargesIncomeGlAccountId;
private String chargesIncomeGlAccountName;
private Long cashSuspenseGlAccountId;
private String cashSuspenseGlAccountName;
private BigDecimal gstPercentage;
private Long gstPayableGlAccountId;
private String gstPayableGlAccountName;
private BigDecimal ddLimitAmount;
}
@@ -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.sponsordd.data;
import java.io.Serializable;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDInventoryData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long sponsorBankId;
private String sponsorBankName;
private Long branchId;
private String branchName;
private Long seriesStart;
private Long seriesEnd;
private Integer totalLeaves;
private LocalDate receivedDate;
private String status;
/** Leaflets in this inventory still in AVAILABLE status (not yet issued/cancelled/stopped). */
private Integer availableLeaves;
}
@@ -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.portfolio.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDIssueRequestData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long leafletId;
private String ddNumber;
private Long clientId;
private String clientName;
private String beneficiaryName;
private String payableAtBranch;
private String payableAtCity;
private BigDecimal faceValue;
private Long issuingBranchId;
private String issuingBranchName;
private LocalDate issuedDate;
private String paymentMethod;
private Long savingsAccountId;
private Long currentAccountId;
private String chequeNumber;
private String chequeBankName;
private String walkinPurchaserName;
private String walkinPurchaserAddress;
private String walkinPurchaserAadhaarMasked;
private BigDecimal chargesAmount;
private BigDecimal gstAmount;
private BigDecimal gstPercentageApplied;
private String status;
private Long makerId;
private String makerUsername;
private LocalDateTime createdDate;
private Long checkerId;
private String checkerUsername;
private LocalDateTime checkerDate;
private String rejectionReason;
private Long resultingSavingsTransactionId;
private Long resultingCurrentTransactionId;
private Long resultingJournalEntryId;
private Long cashReceiptJournalEntryId;
private Long cashRefundJournalEntryId;
/** Groups rows submitted together as one multi-DD slip (see submitIssueRequestBatch). Null for an ordinary single-DD request. */
private Long batchId;
}
@@ -0,0 +1,60 @@
/**
* 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.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDLeafletData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long inventoryId;
private String ddNumber;
private String status;
private BigDecimal faceValue;
private Long issuedToClientId;
private String issuedToClientName;
private String walkinPurchaserName;
private String walkinPurchaserAddress;
private String walkinPurchaserAadhaarMasked;
private String beneficiaryName;
private String payableAtBranch;
private String payableAtCity;
private Long issuingBranchId;
private String issuingBranchName;
private LocalDate issuedDate;
private LocalDate clearedDate;
private String adviceStatus;
private LocalDateTime adviceSentDate;
private Long pendingIssueRequestId;
private Integer printCount;
private Long pendingReprintRequestId;
private Long approvedUnconsumedReprintRequestId;
private Long approverUserId;
private String approverUsername;
}
@@ -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.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDRecoBatchData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String batchReference;
private String fileName;
private String fileHash;
private String status;
private Integer totalRecords;
private BigDecimal totalAmount;
private Integer successCount;
private Integer mismatchCount;
private String createdByUsername;
private LocalDateTime createdDate;
}
@@ -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.portfolio.sponsordd.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDRecoRecordData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long batchId;
private String ddNumber;
private BigDecimal clearingAmount;
private LocalDate clearingDate;
private String clearingRefNumber;
private String matchStatus;
private Long matchedLeafletId;
private String remarks;
private String resolvedByUsername;
private LocalDateTime resolvedDate;
}
@@ -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.portfolio.sponsordd.data;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDReprintRequestData implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long leafletId;
private String ddNumber;
private String reason;
private String status;
private Boolean consumed;
private Long makerId;
private String makerUsername;
private LocalDateTime createdDate;
private Long checkerId;
private String checkerUsername;
private LocalDateTime checkerDate;
private String rejectionReason;
}
@@ -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.sponsordd.data;
import java.math.BigDecimal;
/**
* One line of a multi-DD "slip" - a customer fills one slip listing several drafts (different
* beneficiaries/amounts, charges optional per line) funded from one account debit or one cheque; the
* combined total across every line is what actually gets deducted. Everything else (payment method,
* account, cheque details, issuing branch, issued date) is shared across the whole slip and carried
* separately, not per line.
*/
public record SponsorDDSlipLine(String beneficiaryName, String payableAtBranch, String payableAtCity, BigDecimal faceValue,
BigDecimal chargesAmount) {}
@@ -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.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_bank")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorBank extends AbstractPersistableCustom<Long> {
@Column(name = "name", nullable = false, unique = true, length = 100)
private String name;
@Column(name = "description", length = 255)
private String description;
@Column(name = "ifsc_code", length = 11)
private String ifscCode;
@Column(name = "bank_code", length = 20)
private String bankCode;
@Column(name = "swift_code", length = 11)
private String swiftCode;
@ManyToOne
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
}
@@ -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.portfolio.sponsordd.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorBankRepository extends JpaRepository<SponsorBank, Long>, JpaSpecificationExecutor<SponsorBank> {
Optional<SponsorBank> findByName(String name);
}
@@ -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.
*/
package org.apache.fineract.portfolio.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_audit_log")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDAuditLog extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "leaflet_id", nullable = false)
private SponsorDDLeaflet leaflet;
@Column(name = "action_type", nullable = false, length = 30)
private String actionType;
@Column(name = "pre_status", length = 20)
private String preStatus;
@Column(name = "post_status", nullable = false, length = 20)
private String postStatus;
@ManyToOne
@JoinColumn(name = "changed_by_id", nullable = false)
private AppUser changedBy;
@Column(name = "client_ip", nullable = false, length = 45)
private String clientIp;
@Column(name = "payload_snapshot")
private String payloadSnapshot;
@Column(name = "remarks", length = 255)
private String remarks;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
}
@@ -0,0 +1,25 @@
/**
* 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.sponsordd.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDAuditLogRepository extends JpaRepository<SponsorDDAuditLog, Long>, JpaSpecificationExecutor<SponsorDDAuditLog> {
}
@@ -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.portfolio.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_inventory")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDInventory extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "sponsor_bank_id", nullable = false)
private SponsorBank sponsorBank;
@ManyToOne
@JoinColumn(name = "branch_id", nullable = false)
private Office branch;
@Column(name = "series_start", nullable = false)
private Long seriesStart;
@Column(name = "series_end", nullable = false)
private Long seriesEnd;
@Column(name = "total_leaves", nullable = false)
private Integer totalLeaves;
@Column(name = "received_date", nullable = false)
private LocalDate receivedDate;
@Column(name = "status", nullable = false, length = 20)
private String status;
@ManyToOne
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedBy;
@Column(name = "lastmodified_date")
private LocalDateTime lastModifiedDate;
}
@@ -0,0 +1,25 @@
/**
* 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.sponsordd.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDInventoryRepository extends JpaRepository<SponsorDDInventory, Long>, JpaSpecificationExecutor<SponsorDDInventory> {
}
@@ -0,0 +1,117 @@
/**
* 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.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_leaflet")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDLeaflet extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "inventory_id", nullable = false)
private SponsorDDInventory inventory;
@Column(name = "dd_number", nullable = false, unique = true, length = 30)
private String ddNumber;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "face_value", nullable = false, precision = 19, scale = 6)
private BigDecimal faceValue;
@ManyToOne
@JoinColumn(name = "issued_to_client_id")
private Client issuedToClient;
@Column(name = "beneficiary_name", length = 150)
private String beneficiaryName;
@Column(name = "payable_at_branch", length = 150)
private String payableAtBranch;
@Column(name = "payable_at_city", length = 100)
private String payableAtCity;
/** Set instead of issuedToClient when the purchaser is a walk-in, not a bank client (cash payment). */
@Column(name = "walkin_purchaser_name", length = 150)
private String walkinPurchaserName;
@Column(name = "walkin_purchaser_address", length = 255)
private String walkinPurchaserAddress;
/** Stored masked (only the last 4 digits kept) — never the full Aadhaar number. */
@Column(name = "walkin_purchaser_aadhaar_masked", length = 20)
private String walkinPurchaserAadhaarMasked;
@ManyToOne
@JoinColumn(name = "issuing_branch_id")
private Office issuingBranch;
@Column(name = "issued_date")
private LocalDate issuedDate;
@Column(name = "cleared_date")
private LocalDate clearedDate;
@Column(name = "advice_status", nullable = false, length = 20)
private String adviceStatus;
@Column(name = "advice_sent_date")
private LocalDateTime adviceSentDate;
@Version
@Column(name = "version", nullable = false)
private Integer version;
@ManyToOne
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedBy;
@Column(name = "lastmodified_date")
private LocalDateTime lastModifiedDate;
}
@@ -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.sponsordd.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDLeafletRepository extends JpaRepository<SponsorDDLeaflet, Long>, JpaSpecificationExecutor<SponsorDDLeaflet> {
Optional<SponsorDDLeaflet> findByDdNumber(String ddNumber);
}
@@ -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.portfolio.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_reco_batch")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDRecoBatch extends AbstractPersistableCustom<Long> {
@Column(name = "batch_reference", nullable = false, unique = true, length = 50)
private String batchReference;
@Column(name = "file_name", nullable = false, length = 150)
private String fileName;
@Column(name = "file_hash", nullable = false, length = 64)
private String fileHash;
@Column(name = "status", nullable = false, length = 20)
private String status;
@Column(name = "total_records", nullable = false)
private Integer totalRecords;
@Column(name = "total_amount", nullable = false, precision = 19, scale = 6)
private BigDecimal totalAmount;
@Column(name = "success_count", nullable = false)
private Integer successCount;
@Column(name = "mismatch_count", nullable = false)
private Integer mismatchCount;
@ManyToOne
@JoinColumn(name = "createdby_id", nullable = false)
private AppUser createdBy;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
@ManyToOne
@JoinColumn(name = "lastmodifiedby_id")
private AppUser lastModifiedBy;
@Column(name = "lastmodified_date")
private LocalDateTime lastModifiedDate;
}
@@ -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.sponsordd.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDRecoBatchRepository extends JpaRepository<SponsorDDRecoBatch, Long>, JpaSpecificationExecutor<SponsorDDRecoBatch> {
Optional<SponsorDDRecoBatch> findByFileHash(String fileHash);
Optional<SponsorDDRecoBatch> findByBatchReference(String batchReference);
}
@@ -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.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.useradministration.domain.AppUser;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_reco_record")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDRecoRecord extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "batch_id", nullable = false)
private SponsorDDRecoBatch batch;
@Column(name = "dd_number", nullable = false, length = 30)
private String ddNumber;
@Column(name = "clearing_amount", nullable = false, precision = 19, scale = 6)
private BigDecimal clearingAmount;
@Column(name = "clearing_date", nullable = false)
private LocalDate clearingDate;
@Column(name = "clearing_ref_number", nullable = false, length = 50)
private String clearingRefNumber;
@Column(name = "match_status", nullable = false, length = 25)
private String matchStatus;
@ManyToOne
@JoinColumn(name = "matched_leaflet_id")
private SponsorDDLeaflet matchedLeaflet;
@Column(name = "remarks", length = 255)
private String remarks;
@ManyToOne
@JoinColumn(name = "resolved_by_id")
private AppUser resolvedBy;
@Column(name = "resolved_date")
private LocalDateTime resolvedDate;
}
@@ -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.sponsordd.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDRecoRecordRepository extends JpaRepository<SponsorDDRecoRecord, Long>, JpaSpecificationExecutor<SponsorDDRecoRecord> {
List<SponsorDDRecoRecord> findByBatchId(Long batchId);
}
@@ -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.sponsordd.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
@Getter
@Setter
@Entity
@Table(name = "m_sponsor_dd_transaction")
@NoArgsConstructor
@AllArgsConstructor
public class SponsorDDTransaction extends AbstractPersistableCustom<Long> {
@ManyToOne
@JoinColumn(name = "leaflet_id", nullable = false)
private SponsorDDLeaflet leaflet;
@ManyToOne
@JoinColumn(name = "savings_transaction_id")
private SavingsAccountTransaction savingsTransaction;
@ManyToOne
@JoinColumn(name = "current_transaction_id")
private CurrentAccountTransaction currentTransaction;
@ManyToOne
@JoinColumn(name = "journal_entry_id")
private JournalEntry journalEntry;
@Column(name = "transaction_type", nullable = false, length = 20)
private String transactionType;
@Column(name = "amount", nullable = false, precision = 19, scale = 6)
private BigDecimal amount;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
}
@@ -0,0 +1,25 @@
/**
* 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.sponsordd.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SponsorDDTransactionRepository extends JpaRepository<SponsorDDTransaction, Long>, JpaSpecificationExecutor<SponsorDDTransaction> {
}
@@ -0,0 +1,94 @@
/**
* 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.sponsordd.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDBatchIssueResult;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDSlipLine;
public interface SponsorDDIssueRequestWritePlatformService {
/**
* Clerk (maker) step: records a PENDING issue request.
* clientId is required for ACCOUNT_DEBIT/CHEQUE; for CASH it must be null and the walk-in
* purchaser's own details (name, address, raw Aadhaar — masked to last 4 digits before storage)
* are supplied instead, since the purchaser need not be a bank client at all. For CASH,
* cashierDepositRequestId must reference an already-APPROVED cashier deposit request (submitted
* by the cashier via Transaction Entry, AC.TP=GL, target = the DD Cash Suspense GL account) for
* exactly the face value + charges + GST total - the cash itself is never posted by this class;
* it's only validated and linked here, since the cashier's own maker-checker flow already moved
* the money and updated their tracked cash position.
*/
CommandProcessingResult submitIssueRequest(Long leafletId, Long clientId, String walkinPurchaserName,
String walkinPurchaserAddress, String walkinPurchaserAadhaar, String beneficiaryName, String payableAtBranch,
String payableAtCity, BigDecimal faceValue, BigDecimal chargesAmount, Long issuingBranchId, LocalDate issuedDate,
String paymentMethod, Long savingsAccountId, Long currentAccountId, String chequeNumber, String chequeBankName,
Long cashierDepositRequestId);
/**
* Manager/head-cashier (checker) step: performs the real debit/credit GL posting
* (client account debit, cheque-clearing GL debit, or cash-in-hand GL debit, credited to
* Sponsor DD Payable — plus a Charges Income credit leg if charges apply), then issues the
* demand draft.
*/
CommandProcessingResult approveIssueRequest(Long issueRequestId);
CommandProcessingResult rejectIssueRequest(Long issueRequestId, String rejectionReason);
/**
* Clerk (maker) step for a multi-DD "slip": one customer account debit (or one cheque) funds
* several drafts in one submission - a physical leaflet is auto-allocated per line from the given
* inventory, and every resulting row shares one batch id so the checker approves/rejects the whole
* slip as a unit rather than line by line. CASH is not supported here - only ACCOUNT_DEBIT/CHEQUE,
* since a cash slip would also need Transaction Entry changes to record one lump intake for N
* drafts, which is a separate piece of work. Returns exactly which physical leaflet (DD number)
* was auto-allocated to each line and for what amount, since that allocation only happens inside
* this call (locked against a concurrent slip grabbing the same leaflet) and can't be known by the
* caller beforehand.
*/
SponsorDDBatchIssueResult submitIssueRequestBatch(Long inventoryId, Long clientId, Long issuingBranchId, LocalDate issuedDate,
String paymentMethod, Long savingsAccountId, Long currentAccountId, String chequeNumber, String chequeBankName,
List<SponsorDDSlipLine> lines);
/**
* Checker step for a multi-DD slip: one combined withdrawal for the slip's total face value, one
* combined GL journal entry (Dr control/clearing GL for total face value + charges + GST, Cr DD
* Payable / Charges Income / GST Payable), then every line's leaflet is issued and every row in the
* batch moves to APPROVED together.
*/
CommandProcessingResult approveIssueRequestBatch(Long batchId);
/** Rejects every line of a pending multi-DD slip together; no money has moved yet for ACCOUNT_DEBIT/CHEQUE. */
CommandProcessingResult rejectIssueRequestBatch(Long batchId, String rejectionReason);
/**
* Back-office configuration: which GL accounts to use for a given sponsor bank / payment type,
* the GST rate applied to DD issuance charges, and the DD limit amount - kept here as data so it
* can be updated at any time (e.g. when the government revises the GST rate, or compliance
* revises the DD limit) without a code deployment. ddLimitAmount, if set, caps both a single DD's
* face value and the combined face value of all DDs issued to the same beneficiary on the same
* day; null/unset means no limit is enforced.
*/
CommandProcessingResult upsertGlMapping(Long paymentTypeId, Long ddPayableGlAccountId, Long chequeClearingGlAccountId,
Long cashGlAccountId, Long chargesIncomeGlAccountId, Long cashSuspenseGlAccountId, BigDecimal gstPercentage,
Long gstPayableGlAccountId, BigDecimal ddLimitAmount);
}
@@ -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.sponsordd.service;
import java.util.Collection;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDCashierDepositOptionData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDGlMappingData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDInventoryData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDIssueRequestData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDLeafletData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDReprintRequestData;
public interface SponsorDDReadPlatformService {
Collection<SponsorDDInventoryData> retrieveAllInventories();
SponsorDDInventoryData retrieveInventory(Long inventoryId);
Collection<SponsorDDLeafletData> retrieveAllLeafletsByInventory(Long inventoryId);
SponsorDDLeafletData retrieveLeaflet(Long leafletId);
SponsorDDLeafletData retrieveLeafletByDdNumber(String ddNumber);
Collection<SponsorDDLeafletData> retrieveLeaflets(Long sponsorBankId, String status, String adviceStatus);
Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData> retrieveAllReconciliationBatches();
org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData retrieveReconciliationBatch(Long batchId);
Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData> retrieveReconciliationRecordsByBatch(Long batchId);
Collection<SponsorDDGlMappingData> retrieveAllGlMappings();
SponsorDDGlMappingData retrieveGlMappingBySponsorBank(Long sponsorBankId);
Collection<SponsorDDIssueRequestData> retrieveAllIssueRequests(String status);
Collection<SponsorDDIssueRequestData> retrieveMyIssueRequests(Long makerId);
SponsorDDIssueRequestData retrieveIssueRequest(Long issueRequestId);
Collection<SponsorDDReprintRequestData> retrieveAllReprintRequests(String status);
/**
* Approved cashier Transaction Entry (AC.TP=GL) deposit requests, credited to the DD Cash
* Suspense GL account, and not yet linked to any Sponsor DD issue request - candidates a clerk
* can pick from when submitting a new CASH DD issuance request.
*/
Collection<SponsorDDCashierDepositOptionData> retrieveAvailableCashierDepositRequests();
}
@@ -0,0 +1,465 @@
/**
* 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.sponsordd.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDCashierDepositOptionData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDGlMappingData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDInventoryData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDIssueRequestData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDLeafletData;
import org.apache.fineract.portfolio.sponsordd.data.SponsorDDReprintRequestData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class SponsorDDReadPlatformServiceImpl implements SponsorDDReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final InventoryMapper inventoryMapper = new InventoryMapper();
private final LeafletMapper leafletMapper = new LeafletMapper();
@Autowired
public SponsorDDReadPlatformServiceImpl(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private static final String INVENTORY_SELECT =
"select inv.id, inv.sponsor_bank_id, sb.name as sponsor_bank_name, inv.branch_id, o.name as branch_name, "
+ "inv.series_start, inv.series_end, inv.total_leaves, inv.received_date, inv.status, "
+ "(select count(*) from m_sponsor_dd_leaflet lf where lf.inventory_id = inv.id and lf.status = 'AVAILABLE') as available_leaves "
+ "from m_sponsor_dd_inventory inv "
+ "join m_sponsor_bank sb on sb.id = inv.sponsor_bank_id "
+ "join m_office o on o.id = inv.branch_id ";
@Override
public Collection<SponsorDDInventoryData> retrieveAllInventories() {
final String sql = INVENTORY_SELECT + "order by inv.id desc";
return this.jdbcTemplate.query(sql, this.inventoryMapper);
}
@Override
public SponsorDDInventoryData retrieveInventory(final Long inventoryId) {
try {
final String sql = INVENTORY_SELECT + "where inv.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.inventoryMapper, inventoryId);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final String LEAFLET_SELECT =
"select leaf.id, leaf.inventory_id, leaf.dd_number, leaf.status, leaf.face_value, leaf.issued_to_client_id, "
+ "c.display_name as client_name, leaf.walkin_purchaser_name, leaf.walkin_purchaser_address, "
+ "leaf.walkin_purchaser_aadhaar_masked, leaf.beneficiary_name, leaf.payable_at_branch, leaf.payable_at_city, "
+ "leaf.issuing_branch_id, o.name as branch_name, "
+ "leaf.issued_date, leaf.cleared_date, leaf.advice_status, leaf.advice_sent_date, pir.id as pending_issue_request_id, "
+ "leaf.print_count, prr.id as pending_reprint_request_id, arr.id as approved_reprint_request_id, "
+ "apr.checker_id as approver_user_id, approver.username as approver_username "
+ "from m_sponsor_dd_leaflet leaf "
+ "left join m_client c on c.id = leaf.issued_to_client_id "
+ "left join m_office o on o.id = leaf.issuing_branch_id "
+ "left join m_sponsor_dd_issue_request pir on pir.leaflet_id = leaf.id and pir.status = 'PENDING' "
+ "left join m_sponsor_dd_reprint_request prr on prr.leaflet_id = leaf.id and prr.status = 'PENDING' "
+ "left join m_sponsor_dd_reprint_request arr on arr.leaflet_id = leaf.id and arr.status = 'APPROVED' and arr.consumed = false "
+ "left join m_sponsor_dd_issue_request apr on apr.leaflet_id = leaf.id and apr.status = 'APPROVED' "
+ "left join m_appuser approver on approver.id = apr.checker_id ";
@Override
public Collection<SponsorDDLeafletData> retrieveAllLeafletsByInventory(final Long inventoryId) {
final String sql = LEAFLET_SELECT + "where leaf.inventory_id = ? order by leaf.dd_number asc";
return this.jdbcTemplate.query(sql, this.leafletMapper, inventoryId);
}
@Override
public SponsorDDLeafletData retrieveLeaflet(final Long leafletId) {
try {
final String sql = LEAFLET_SELECT + "where leaf.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.leafletMapper, leafletId);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
@Override
public SponsorDDLeafletData retrieveLeafletByDdNumber(final String ddNumber) {
try {
final String sql = LEAFLET_SELECT + "where leaf.dd_number = ?";
return this.jdbcTemplate.queryForObject(sql, this.leafletMapper, ddNumber);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final class InventoryMapper implements RowMapper<SponsorDDInventoryData> {
@Override
public SponsorDDInventoryData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long sponsorBankId = rs.getLong("sponsor_bank_id");
final String sponsorBankName = rs.getString("sponsor_bank_name");
final Long branchId = rs.getLong("branch_id");
final String branchName = rs.getString("branch_name");
final Long seriesStart = rs.getLong("series_start");
final Long seriesEnd = rs.getLong("series_end");
final Integer totalLeaves = rs.getInt("total_leaves");
final LocalDate receivedDate = rs.getDate("received_date") != null ? rs.getDate("received_date").toLocalDate() : null;
final String status = rs.getString("status");
final Integer availableLeaves = rs.getInt("available_leaves");
return new SponsorDDInventoryData(id, sponsorBankId, sponsorBankName, branchId, branchName, seriesStart, seriesEnd, totalLeaves, receivedDate, status, availableLeaves);
}
}
private static final class LeafletMapper implements RowMapper<SponsorDDLeafletData> {
@Override
public SponsorDDLeafletData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long inventoryId = rs.getLong("inventory_id");
final String ddNumber = rs.getString("dd_number");
final String status = rs.getString("status");
final BigDecimal faceValue = rs.getBigDecimal("face_value");
final Long issuedToClientId = rs.getObject("issued_to_client_id") != null ? rs.getLong("issued_to_client_id") : null;
final String clientName = rs.getString("client_name");
final String walkinPurchaserName = rs.getString("walkin_purchaser_name");
final String walkinPurchaserAddress = rs.getString("walkin_purchaser_address");
final String walkinPurchaserAadhaarMasked = rs.getString("walkin_purchaser_aadhaar_masked");
final String beneficiaryName = rs.getString("beneficiary_name");
final String payableAtBranch = rs.getString("payable_at_branch");
final String payableAtCity = rs.getString("payable_at_city");
final Long issuingBranchId = rs.getObject("issuing_branch_id") != null ? rs.getLong("issuing_branch_id") : null;
final String branchName = rs.getString("branch_name");
final LocalDate issuedDate = rs.getDate("issued_date") != null ? rs.getDate("issued_date").toLocalDate() : null;
final LocalDate clearedDate = rs.getDate("cleared_date") != null ? rs.getDate("cleared_date").toLocalDate() : null;
final String adviceStatus = rs.getString("advice_status");
final LocalDateTime adviceSentDate = rs.getTimestamp("advice_sent_date") != null ? rs.getTimestamp("advice_sent_date").toLocalDateTime() : null;
final Long pendingIssueRequestId = rs.getObject("pending_issue_request_id") != null ? rs.getLong("pending_issue_request_id") : null;
final Integer printCount = rs.getObject("print_count") != null ? rs.getInt("print_count") : 0;
final Long pendingReprintRequestId = rs.getObject("pending_reprint_request_id") != null ? rs.getLong("pending_reprint_request_id") : null;
final Long approvedUnconsumedReprintRequestId = rs.getObject("approved_reprint_request_id") != null ? rs.getLong("approved_reprint_request_id") : null;
final Long approverUserId = rs.getObject("approver_user_id") != null ? rs.getLong("approver_user_id") : null;
final String approverUsername = rs.getString("approver_username");
return new SponsorDDLeafletData(id, inventoryId, ddNumber, status, faceValue, issuedToClientId, clientName,
walkinPurchaserName, walkinPurchaserAddress, walkinPurchaserAadhaarMasked, beneficiaryName, payableAtBranch,
payableAtCity, issuingBranchId, branchName, issuedDate, clearedDate, adviceStatus, adviceSentDate,
pendingIssueRequestId, printCount, pendingReprintRequestId, approvedUnconsumedReprintRequestId,
approverUserId, approverUsername);
}
}
@Override
public Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData> retrieveAllReconciliationBatches() {
final String sql = "select b.id, b.batch_reference, b.file_name, b.file_hash, b.status, b.total_records, b.total_amount, b.success_count, b.mismatch_count, u.username as maker_username, b.created_date from m_sponsor_dd_reco_batch b join m_appuser u on u.id = b.createdby_id order by b.id desc";
return this.jdbcTemplate.query(sql, new RecoBatchMapper());
}
@Override
public org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData retrieveReconciliationBatch(final Long batchId) {
try {
final String sql = "select b.id, b.batch_reference, b.file_name, b.file_hash, b.status, b.total_records, b.total_amount, b.success_count, b.mismatch_count, u.username as maker_username, b.created_date from m_sponsor_dd_reco_batch b join m_appuser u on u.id = b.createdby_id where b.id = ?";
return this.jdbcTemplate.queryForObject(sql, new RecoBatchMapper(), batchId);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
@Override
public Collection<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData> retrieveReconciliationRecordsByBatch(final Long batchId) {
final String sql = "select r.id, r.batch_id, r.dd_number, r.clearing_amount, r.clearing_date, r.clearing_ref_number, r.match_status, r.matched_leaflet_id, r.remarks, u.username as resolver_username, r.resolved_date from m_sponsor_dd_reco_record r left join m_appuser u on u.id = r.resolved_by_id where r.batch_id = ? order by r.id asc";
return this.jdbcTemplate.query(sql, new RecoRecordMapper(), batchId);
}
private static final class RecoBatchMapper implements RowMapper<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData> {
@Override
public org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String batchReference = rs.getString("batch_reference");
final String fileName = rs.getString("file_name");
final String fileHash = rs.getString("file_hash");
final String status = rs.getString("status");
final Integer totalRecords = rs.getInt("total_records");
final BigDecimal totalAmount = rs.getBigDecimal("total_amount");
final Integer successCount = rs.getInt("success_count");
final Integer mismatchCount = rs.getInt("mismatch_count");
final String makerUsername = rs.getString("maker_username");
final LocalDateTime createdDate = rs.getTimestamp("created_date") != null ? rs.getTimestamp("created_date").toLocalDateTime() : null;
return new org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoBatchData(id, batchReference, fileName, fileHash, status, totalRecords, totalAmount, successCount, mismatchCount, makerUsername, createdDate);
}
}
private static final class RecoRecordMapper implements RowMapper<org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData> {
@Override
public org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long batchId = rs.getLong("batch_id");
final String ddNumber = rs.getString("dd_number");
final BigDecimal clearingAmount = rs.getBigDecimal("clearing_amount");
final LocalDate clearingDate = rs.getDate("clearing_date") != null ? rs.getDate("clearing_date").toLocalDate() : null;
final String clearingRefNumber = rs.getString("clearing_ref_number");
final String matchStatus = rs.getString("match_status");
final Long matchedLeafletId = rs.getObject("matched_leaflet_id") != null ? rs.getLong("matched_leaflet_id") : null;
final String remarks = rs.getString("remarks");
final String resolverUsername = rs.getString("resolver_username");
final LocalDateTime resolvedDate = rs.getTimestamp("resolved_date") != null ? rs.getTimestamp("resolved_date").toLocalDateTime() : null;
return new org.apache.fineract.portfolio.sponsordd.data.SponsorDDRecoRecordData(id, batchId, ddNumber, clearingAmount, clearingDate, clearingRefNumber, matchStatus, matchedLeafletId, remarks, resolverUsername, resolvedDate);
}
}
private static final String GL_MAPPING_SELECT =
"select m.id, null as sponsor_bank_id, 'Global' as sponsor_bank_name, m.dd_payable_gl_account_id, "
+ "payable.name as dd_payable_gl_account_name, m.cheque_clearing_gl_account_id, clearing.name as cheque_clearing_gl_account_name, "
+ "m.cash_gl_account_id, cash.name as cash_gl_account_name, m.charges_income_gl_account_id, charges.name as charges_income_gl_account_name, "
+ "m.cash_suspense_gl_account_id, suspense.name as cash_suspense_gl_account_name, "
+ "m.gst_percentage, m.gst_payable_gl_account_id, gst.name as gst_payable_gl_account_name, "
+ "m.dd_limit_amount "
+ "from m_sponsor_dd_gl_mapping m "
+ "join acc_gl_account payable on payable.id = m.dd_payable_gl_account_id "
+ "left join acc_gl_account clearing on clearing.id = m.cheque_clearing_gl_account_id "
+ "left join acc_gl_account cash on cash.id = m.cash_gl_account_id "
+ "left join acc_gl_account charges on charges.id = m.charges_income_gl_account_id "
+ "left join acc_gl_account suspense on suspense.id = m.cash_suspense_gl_account_id "
+ "left join acc_gl_account gst on gst.id = m.gst_payable_gl_account_id ";
@Override
public Collection<SponsorDDGlMappingData> retrieveAllGlMappings() {
final String sql = GL_MAPPING_SELECT;
return this.jdbcTemplate.query(sql, new GlMappingMapper());
}
@Override
public SponsorDDGlMappingData retrieveGlMappingBySponsorBank(final Long sponsorBankId) {
try {
final String sql = GL_MAPPING_SELECT + "limit 1";
return this.jdbcTemplate.queryForObject(sql, new GlMappingMapper());
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final class GlMappingMapper implements RowMapper<SponsorDDGlMappingData> {
@Override
public SponsorDDGlMappingData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long sponsorBankId = rs.getObject("sponsor_bank_id") != null ? rs.getLong("sponsor_bank_id") : null;
final String sponsorBankName = rs.getString("sponsor_bank_name");
final Long ddPayableGlAccountId = rs.getLong("dd_payable_gl_account_id");
final String ddPayableGlAccountName = rs.getString("dd_payable_gl_account_name");
final Long chequeClearingGlAccountId = rs.getObject("cheque_clearing_gl_account_id") != null ? rs.getLong("cheque_clearing_gl_account_id") : null;
final String chequeClearingGlAccountName = rs.getString("cheque_clearing_gl_account_name");
final Long cashGlAccountId = rs.getObject("cash_gl_account_id") != null ? rs.getLong("cash_gl_account_id") : null;
final String cashGlAccountName = rs.getString("cash_gl_account_name");
final Long chargesIncomeGlAccountId = rs.getObject("charges_income_gl_account_id") != null ? rs.getLong("charges_income_gl_account_id") : null;
final String chargesIncomeGlAccountName = rs.getString("charges_income_gl_account_name");
final Long cashSuspenseGlAccountId = rs.getObject("cash_suspense_gl_account_id") != null ? rs.getLong("cash_suspense_gl_account_id") : null;
final String cashSuspenseGlAccountName = rs.getString("cash_suspense_gl_account_name");
final BigDecimal gstPercentage = rs.getBigDecimal("gst_percentage");
final Long gstPayableGlAccountId = rs.getObject("gst_payable_gl_account_id") != null ? rs.getLong("gst_payable_gl_account_id") : null;
final String gstPayableGlAccountName = rs.getString("gst_payable_gl_account_name");
final BigDecimal ddLimitAmount = rs.getBigDecimal("dd_limit_amount");
return new SponsorDDGlMappingData(id, sponsorBankId, sponsorBankName, ddPayableGlAccountId, ddPayableGlAccountName,
chequeClearingGlAccountId, chequeClearingGlAccountName, cashGlAccountId, cashGlAccountName,
chargesIncomeGlAccountId, chargesIncomeGlAccountName, cashSuspenseGlAccountId, cashSuspenseGlAccountName,
gstPercentage, gstPayableGlAccountId, gstPayableGlAccountName, ddLimitAmount);
}
}
private static final String ISSUE_REQUEST_SELECT =
"select r.id, r.leaflet_id, leaf.dd_number, r.client_id, c.display_name as client_name, r.walkin_purchaser_name, "
+ "r.walkin_purchaser_address, r.walkin_purchaser_aadhaar_masked, r.beneficiary_name, r.payable_at_branch, "
+ "r.payable_at_city, "
+ "r.face_value, r.issuing_branch_id, o.name as branch_name, r.issued_date, r.payment_method, r.savings_account_id, "
+ "r.current_account_id, r.cheque_number, r.cheque_bank_name, r.charges_amount, r.gst_amount, r.gst_percentage_applied, "
+ "r.status, r.maker_id, maker.username as maker_username, "
+ "r.created_date, r.checker_id, checker.username as checker_username, r.checker_date, r.rejection_reason, "
+ "r.resulting_savings_transaction_id, r.resulting_current_transaction_id, r.resulting_journal_entry_id, "
+ "r.cash_receipt_journal_entry_id, r.cash_refund_journal_entry_id, r.batch_id "
+ "from m_sponsor_dd_issue_request r "
+ "join m_sponsor_dd_leaflet leaf on leaf.id = r.leaflet_id "
+ "left join m_client c on c.id = r.client_id "
+ "join m_office o on o.id = r.issuing_branch_id "
+ "join m_appuser maker on maker.id = r.maker_id "
+ "left join m_appuser checker on checker.id = r.checker_id ";
@Override
public Collection<SponsorDDIssueRequestData> retrieveAllIssueRequests(final String status) {
if (status != null && !status.isBlank()) {
final String sql = ISSUE_REQUEST_SELECT + "where r.status = ? order by r.id desc";
return this.jdbcTemplate.query(sql, new IssueRequestMapper(), status);
}
final String sql = ISSUE_REQUEST_SELECT + "order by r.id desc";
return this.jdbcTemplate.query(sql, new IssueRequestMapper());
}
@Override
public Collection<SponsorDDIssueRequestData> retrieveMyIssueRequests(final Long makerId) {
final String sql = ISSUE_REQUEST_SELECT + "where r.maker_id = ? order by r.id desc";
return this.jdbcTemplate.query(sql, new IssueRequestMapper(), makerId);
}
@Override
public SponsorDDIssueRequestData retrieveIssueRequest(final Long issueRequestId) {
try {
final String sql = ISSUE_REQUEST_SELECT + "where r.id = ?";
return this.jdbcTemplate.queryForObject(sql, new IssueRequestMapper(), issueRequestId);
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
private static final class IssueRequestMapper implements RowMapper<SponsorDDIssueRequestData> {
@Override
public SponsorDDIssueRequestData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long leafletId = rs.getLong("leaflet_id");
final String ddNumber = rs.getString("dd_number");
final Long clientId = rs.getObject("client_id") != null ? rs.getLong("client_id") : null;
final String clientName = rs.getString("client_name");
final String walkinPurchaserName = rs.getString("walkin_purchaser_name");
final String walkinPurchaserAddress = rs.getString("walkin_purchaser_address");
final String walkinPurchaserAadhaarMasked = rs.getString("walkin_purchaser_aadhaar_masked");
final String beneficiaryName = rs.getString("beneficiary_name");
final String payableAtBranch = rs.getString("payable_at_branch");
final String payableAtCity = rs.getString("payable_at_city");
final BigDecimal faceValue = rs.getBigDecimal("face_value");
final Long issuingBranchId = rs.getLong("issuing_branch_id");
final String branchName = rs.getString("branch_name");
final LocalDate issuedDate = rs.getDate("issued_date") != null ? rs.getDate("issued_date").toLocalDate() : null;
final String paymentMethod = rs.getString("payment_method");
final Long savingsAccountId = rs.getObject("savings_account_id") != null ? rs.getLong("savings_account_id") : null;
final Long currentAccountId = rs.getObject("current_account_id") != null ? rs.getLong("current_account_id") : null;
final String chequeNumber = rs.getString("cheque_number");
final String chequeBankName = rs.getString("cheque_bank_name");
final BigDecimal chargesAmount = rs.getBigDecimal("charges_amount");
final BigDecimal gstAmount = rs.getBigDecimal("gst_amount");
final BigDecimal gstPercentageApplied = rs.getBigDecimal("gst_percentage_applied");
final String status = rs.getString("status");
final Long makerId = rs.getLong("maker_id");
final String makerUsername = rs.getString("maker_username");
final LocalDateTime createdDate = rs.getTimestamp("created_date") != null ? rs.getTimestamp("created_date").toLocalDateTime() : null;
final Long checkerId = rs.getObject("checker_id") != null ? rs.getLong("checker_id") : null;
final String checkerUsername = rs.getString("checker_username");
final LocalDateTime checkerDate = rs.getTimestamp("checker_date") != null ? rs.getTimestamp("checker_date").toLocalDateTime() : null;
final String rejectionReason = rs.getString("rejection_reason");
final Long resultingSavingsTransactionId = rs.getObject("resulting_savings_transaction_id") != null ? rs.getLong("resulting_savings_transaction_id") : null;
final Long resultingCurrentTransactionId = rs.getObject("resulting_current_transaction_id") != null ? rs.getLong("resulting_current_transaction_id") : null;
final Long resultingJournalEntryId = rs.getObject("resulting_journal_entry_id") != null ? rs.getLong("resulting_journal_entry_id") : null;
final Long cashReceiptJournalEntryId = rs.getObject("cash_receipt_journal_entry_id") != null ? rs.getLong("cash_receipt_journal_entry_id") : null;
final Long cashRefundJournalEntryId = rs.getObject("cash_refund_journal_entry_id") != null ? rs.getLong("cash_refund_journal_entry_id") : null;
final Long batchId = rs.getObject("batch_id") != null ? rs.getLong("batch_id") : null;
return new SponsorDDIssueRequestData(id, leafletId, ddNumber, clientId, clientName, beneficiaryName, payableAtBranch,
payableAtCity, faceValue, issuingBranchId, branchName, issuedDate, paymentMethod, savingsAccountId,
currentAccountId, chequeNumber, chequeBankName, walkinPurchaserName, walkinPurchaserAddress,
walkinPurchaserAadhaarMasked, chargesAmount, gstAmount, gstPercentageApplied, status, makerId, makerUsername,
createdDate, checkerId, checkerUsername, checkerDate, rejectionReason, resultingSavingsTransactionId,
resultingCurrentTransactionId, resultingJournalEntryId, cashReceiptJournalEntryId, cashRefundJournalEntryId,
batchId);
}
}
private static final String REPRINT_REQUEST_SELECT =
"select rr.id, rr.leaflet_id, leaf.dd_number, rr.reason, rr.status, rr.consumed, rr.maker_id, "
+ "maker.username as maker_username, rr.created_date, rr.checker_id, checker.username as checker_username, "
+ "rr.checker_date, rr.rejection_reason "
+ "from m_sponsor_dd_reprint_request rr "
+ "join m_sponsor_dd_leaflet leaf on leaf.id = rr.leaflet_id "
+ "join m_appuser maker on maker.id = rr.maker_id "
+ "left join m_appuser checker on checker.id = rr.checker_id ";
@Override
public Collection<SponsorDDReprintRequestData> retrieveAllReprintRequests(final String status) {
if (status != null && !status.isBlank()) {
final String sql = REPRINT_REQUEST_SELECT + "where rr.status = ? order by rr.id desc";
return this.jdbcTemplate.query(sql, new ReprintRequestMapper(), status);
}
final String sql = REPRINT_REQUEST_SELECT + "order by rr.id desc";
return this.jdbcTemplate.query(sql, new ReprintRequestMapper());
}
private static final class ReprintRequestMapper implements RowMapper<SponsorDDReprintRequestData> {
@Override
public SponsorDDReprintRequestData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final Long leafletId = rs.getLong("leaflet_id");
final String ddNumber = rs.getString("dd_number");
final String reason = rs.getString("reason");
final String status = rs.getString("status");
final Boolean consumed = rs.getBoolean("consumed");
final Long makerId = rs.getLong("maker_id");
final String makerUsername = rs.getString("maker_username");
final LocalDateTime createdDate = rs.getTimestamp("created_date") != null ? rs.getTimestamp("created_date").toLocalDateTime() : null;
final Long checkerId = rs.getObject("checker_id") != null ? rs.getLong("checker_id") : null;
final String checkerUsername = rs.getString("checker_username");
final LocalDateTime checkerDate = rs.getTimestamp("checker_date") != null ? rs.getTimestamp("checker_date").toLocalDateTime() : null;
final String rejectionReason = rs.getString("rejection_reason");
return new SponsorDDReprintRequestData(id, leafletId, ddNumber, reason, status, consumed, makerId, makerUsername,
createdDate, checkerId, checkerUsername, checkerDate, rejectionReason);
}
}
@Override
public Collection<SponsorDDLeafletData> retrieveLeaflets(final Long sponsorBankId, final String status, final String adviceStatus) {
final List<Object> params = new ArrayList<>();
String sql = LEAFLET_SELECT + "join m_sponsor_dd_inventory inv on inv.id = leaf.inventory_id WHERE 1=1 ";
if (sponsorBankId != null) {
sql += " AND inv.sponsor_bank_id = ? ";
params.add(sponsorBankId);
}
if (status != null) {
sql += " AND leaf.status = ? ";
params.add(status);
}
if (adviceStatus != null) {
sql += " AND leaf.advice_status = ? ";
params.add(adviceStatus);
}
sql += " ORDER BY leaf.dd_number ASC";
return this.jdbcTemplate.query(sql, this.leafletMapper, params.toArray());
}
@Override
public Collection<SponsorDDCashierDepositOptionData> retrieveAvailableCashierDepositRequests() {
final String sql = "select cdr.id, cdr.voucher_no, cdr.amount, cdr.description, cdr.cashier_username, cdr.actioned_at, "
+ "cdr.dd_face_value, cdr.dd_charges, cdr.dd_gst_percentage, cdr.dd_gst_amount "
+ "from m_cashier_deposit_request cdr "
+ "where cdr.status = 'APPROVED' "
+ "and cdr.gl_account_id = (select cash_suspense_gl_account_id from m_sponsor_dd_gl_mapping limit 1) "
+ "and cdr.id not in (select cashier_deposit_request_id from m_sponsor_dd_issue_request "
+ "where cashier_deposit_request_id is not null) "
+ "order by cdr.actioned_at desc";
return this.jdbcTemplate.query(sql, (rs, rowNum) -> new SponsorDDCashierDepositOptionData(
rs.getLong("id"),
rs.getString("voucher_no"),
rs.getBigDecimal("amount"),
rs.getString("description"),
rs.getString("cashier_username"),
rs.getTimestamp("actioned_at") != null ? rs.getTimestamp("actioned_at").toLocalDateTime() : null,
rs.getBigDecimal("dd_face_value"),
rs.getBigDecimal("dd_charges"),
rs.getBigDecimal("dd_gst_percentage"),
rs.getBigDecimal("dd_gst_amount")));
}
}
@@ -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.sponsordd.service;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoBatch;
public interface SponsorDDReconciliationService {
SponsorDDRecoBatch uploadAndReconcile(String fileName, String fileContent);
CommandProcessingResult resolveMismatch(Long recordId, String actionType, Long resolvedSavingsTxnId, Long resolvedCurrentTxnId);
}
@@ -0,0 +1,361 @@
/**
* 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.sponsordd.service;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDAuditLog;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDAuditLogRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDLeaflet;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDLeafletRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoBatch;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoBatchRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoRecord;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDRecoRecordRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDTransaction;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDTransactionRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SponsorDDReconciliationServiceImpl implements SponsorDDReconciliationService {
private final PlatformSecurityContext context;
private final SponsorDDRecoBatchRepository batchRepository;
private final SponsorDDRecoRecordRepository recordRepository;
private final SponsorDDLeafletRepository leafletRepository;
private final SponsorDDTransactionRepository transactionRepository;
private final SponsorDDAuditLogRepository auditLogRepository;
private final SavingsAccountTransactionRepository savingsTransactionRepository;
private final CurrentAccountTransactionRepository currentTransactionRepository;
@Autowired
public SponsorDDReconciliationServiceImpl(
final PlatformSecurityContext context,
final SponsorDDRecoBatchRepository batchRepository,
final SponsorDDRecoRecordRepository recordRepository,
final SponsorDDLeafletRepository leafletRepository,
final SponsorDDTransactionRepository transactionRepository,
final SponsorDDAuditLogRepository auditLogRepository,
final SavingsAccountTransactionRepository savingsTransactionRepository,
final CurrentAccountTransactionRepository currentTransactionRepository) {
this.context = context;
this.batchRepository = batchRepository;
this.recordRepository = recordRepository;
this.leafletRepository = leafletRepository;
this.transactionRepository = transactionRepository;
this.auditLogRepository = auditLogRepository;
this.savingsTransactionRepository = savingsTransactionRepository;
this.currentTransactionRepository = currentTransactionRepository;
}
@Override
@Transactional
public SponsorDDRecoBatch uploadAndReconcile(final String fileName, final String fileContent) {
final AppUser currentUser = this.context.authenticatedUser();
// 1. Check file uniqueness using SHA-256 hash
final String fileHash = calculateSHA256(fileContent);
final Optional<SponsorDDRecoBatch> existingBatch = this.batchRepository.findByFileHash(fileHash);
if (existingBatch.isPresent()) {
throw new IllegalStateException("This clearing file has already been uploaded and processed under reference: "
+ existingBatch.get().getBatchReference());
}
// 2. Parse CSV rows
final String[] rows = fileContent.split("\\r?\\n");
final List<ParsedRecord> parsedRecords = new ArrayList<>();
BigDecimal totalAmount = BigDecimal.ZERO;
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < rows.length; i++) {
final String row = rows[i].trim();
if (row.isEmpty() || i == 0 && row.toLowerCase().contains("dd_number")) {
// Skip empty lines or CSV header
continue;
}
final String[] columns = row.split(",");
if (columns.length < 4) {
// Malformed row
continue;
}
final String ddNumber = columns[0].trim();
final BigDecimal clearingAmount = new BigDecimal(columns[1].trim());
final LocalDate clearingDate = LocalDate.parse(columns[2].trim(), dateFormatter);
final String clearingRef = columns[3].trim();
parsedRecords.add(new ParsedRecord(ddNumber, clearingAmount, clearingDate, clearingRef));
totalAmount = totalAmount.add(clearingAmount);
}
// 3. Create batch record
final String batchRef = "RECO-" + LocalDate.now().toString().replace("-", "") + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
SponsorDDRecoBatch batch = new SponsorDDRecoBatch(
batchRef,
fileName,
fileHash,
"PROCESSING",
parsedRecords.size(),
totalAmount,
0,
0,
currentUser,
LocalDateTime.now(),
null,
null
);
batch = this.batchRepository.saveAndFlush(batch);
int successCount = 0;
int mismatchCount = 0;
final List<SponsorDDRecoRecord> recoRecords = new ArrayList<>();
// 4. Run matching rules
for (final ParsedRecord pr : parsedRecords) {
final Optional<SponsorDDLeaflet> optLeaflet = this.leafletRepository.findByDdNumber(pr.ddNumber);
String matchStatus = "MATCHED";
SponsorDDLeaflet leaflet = null;
String remarks = null;
if (optLeaflet.isPresent()) {
leaflet = optLeaflet.get();
if ("CLEARED".equals(leaflet.getStatus())) {
matchStatus = "DUPLICATE_CLEARING";
remarks = "Instrument was already cleared on " + leaflet.getClearedDate();
mismatchCount++;
} else if (!"ISSUED".equals(leaflet.getStatus())) {
matchStatus = "UNADVISED_CLEARING";
remarks = "Leaflet status is " + leaflet.getStatus() + " instead of ISSUED";
mismatchCount++;
} else if (leaflet.getFaceValue().compareTo(pr.clearingAmount) != 0) {
matchStatus = "AMOUNT_MISMATCH";
remarks = "Amount mismatch: expected " + leaflet.getFaceValue() + ", cleared " + pr.clearingAmount;
mismatchCount++;
} else {
// Rule 1: Exact Match (Success)
successCount++;
leaflet.setStatus("CLEARED");
leaflet.setClearedDate(pr.clearingDate);
leaflet.setLastModifiedBy(currentUser);
leaflet.setLastModifiedDate(LocalDateTime.now());
this.leafletRepository.save(leaflet);
// Create clearing transaction mapping
final SponsorDDTransaction ddTxn = new SponsorDDTransaction(
leaflet,
null,
null,
null,
"CLEARING",
pr.clearingAmount,
LocalDateTime.now()
);
this.transactionRepository.save(ddTxn);
// Audit Log
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"CLEARING_POSTED",
"ISSUED",
"CLEARED",
currentUser,
"127.0.0.1",
null,
"Sponsor clearing scroll auto-match. Ref: " + pr.clearingRef,
LocalDateTime.now()
);
this.auditLogRepository.save(auditLog);
}
} else {
// Rule 3: Unadvised / Not found
matchStatus = "UNADVISED_CLEARING";
remarks = "DD number not found in internal leaflet registry";
mismatchCount++;
}
final SponsorDDRecoRecord record = new SponsorDDRecoRecord(
batch,
pr.ddNumber,
pr.clearingAmount,
pr.clearingDate,
pr.clearingRef,
matchStatus,
leaflet,
remarks,
null,
null
);
recoRecords.add(record);
}
this.recordRepository.saveAllAndFlush(recoRecords);
// 5. Update batch totals
batch.setSuccessCount(successCount);
batch.setMismatchCount(mismatchCount);
batch.setStatus(mismatchCount > 0 ? "PARTIALLY_RECONCILED" : "RECONCILED");
batch.setLastModifiedBy(currentUser);
batch.setLastModifiedDate(LocalDateTime.now());
this.batchRepository.save(batch);
return batch;
}
@Override
@Transactional
public CommandProcessingResult resolveMismatch(final Long recordId, final String actionType,
final Long resolvedSavingsTxnId, final Long resolvedCurrentTxnId) {
final AppUser currentUser = this.context.authenticatedUser();
final SponsorDDRecoRecord record = this.recordRepository.findById(recordId)
.orElseThrow(() -> new IllegalArgumentException("Reconciliation record not found."));
if (!"AMOUNT_MISMATCH".equals(record.getMatchStatus()) && !"UNADVISED_CLEARING".equals(record.getMatchStatus())) {
throw new IllegalStateException("Only exception records can be resolved. Current status: " + record.getMatchStatus());
}
final SponsorDDRecoBatch batch = record.getBatch();
SavingsAccountTransaction savingsTxn = null;
if (resolvedSavingsTxnId != null) {
savingsTxn = this.savingsTransactionRepository.findById(resolvedSavingsTxnId)
.orElseThrow(() -> new IllegalArgumentException("Resolved savings transaction not found."));
}
CurrentAccountTransaction currentTxn = null;
if (resolvedCurrentTxnId != null) {
currentTxn = this.currentTransactionRepository.findById(resolvedCurrentTxnId)
.orElseThrow(() -> new IllegalArgumentException("Resolved current account transaction not found."));
}
if ("FORCE_MATCH".equals(actionType)) {
final SponsorDDLeaflet leaflet = record.getMatchedLeaflet();
if (leaflet != null) {
final String preStatus = leaflet.getStatus();
leaflet.setStatus("CLEARED");
leaflet.setClearedDate(record.getClearingDate());
leaflet.setLastModifiedBy(currentUser);
leaflet.setLastModifiedDate(LocalDateTime.now());
this.leafletRepository.saveAndFlush(leaflet);
// Create transaction link
final SponsorDDTransaction ddTxn = new SponsorDDTransaction(
leaflet,
savingsTxn,
currentTxn,
null,
"CLEARING",
record.getClearingAmount(),
LocalDateTime.now()
);
this.transactionRepository.save(ddTxn);
// Audit Log
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"CLEARING_POSTED",
preStatus,
"CLEARED",
currentUser,
"127.0.0.1",
null,
"Manual reconciliation override (Force Match) approved by checker.",
LocalDateTime.now()
);
this.auditLogRepository.save(auditLog);
}
record.setMatchStatus("RESOLVED");
record.setRemarks("Manually resolved by force-match override.");
record.setResolvedBy(currentUser);
record.setResolvedDate(LocalDateTime.now());
this.recordRepository.save(record);
// Update batch counts
batch.setSuccessCount(batch.getSuccessCount() + 1);
batch.setMismatchCount(batch.getMismatchCount() - 1);
if (batch.getMismatchCount() == 0) {
batch.setStatus("RECONCILED");
}
batch.setLastModifiedBy(currentUser);
batch.setLastModifiedDate(LocalDateTime.now());
this.batchRepository.save(batch);
}
return new CommandProcessingResultBuilder()
.withEntityId(record.getId())
.build();
}
private String calculateSHA256(final String text) {
try {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
final StringBuilder hexString = new StringBuilder();
for (final byte b : hash) {
final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not available.", e);
}
}
private static final class ParsedRecord {
private final String ddNumber;
private final BigDecimal clearingAmount;
private final LocalDate clearingDate;
private final String clearingRef;
public ParsedRecord(final String ddNumber, final BigDecimal clearingAmount, final LocalDate clearingDate, final String clearingRef) {
this.ddNumber = ddNumber;
this.clearingAmount = clearingAmount;
this.clearingDate = clearingDate;
this.clearingRef = clearingRef;
}
}
}
@@ -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.portfolio.sponsordd.service;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface SponsorDDReprintRequestWritePlatformService {
/**
* Records a print of the given leaflet. The first print of a leaflet is always allowed. Any
* print after that requires a previously APPROVED, not-yet-consumed reprint request - if one
* exists it is consumed by this call; otherwise this throws, and the caller should submit a
* reprint request instead via {@link #requestReprint}.
*/
CommandProcessingResult recordPrint(Long leafletId);
/** Clerk (maker) step: requests permission to print an already-printed leaflet again. */
CommandProcessingResult requestReprint(Long leafletId, String reason);
/** Manager (checker) step: approves a pending reprint request, allowing one further print. */
CommandProcessingResult approveReprintRequest(Long reprintRequestId);
CommandProcessingResult rejectReprintRequest(Long reprintRequestId, String rejectionReason);
}
@@ -0,0 +1,195 @@
/**
* 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.sponsordd.service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class SponsorDDReprintRequestWritePlatformServiceImpl implements SponsorDDReprintRequestWritePlatformService {
private static final String STATUS_PENDING = "PENDING";
private static final String STATUS_APPROVED = "APPROVED";
private static final String STATUS_REJECTED = "REJECTED";
private final JdbcTemplate jdbcTemplate;
private final PlatformSecurityContext context;
@Override
@Transactional
public CommandProcessingResult recordPrint(final Long leafletId) {
final Map<String, Object> leaflet = findLeafletOrThrow(leafletId);
if (!"ISSUED".equals(leaflet.get("status"))) {
throw new IllegalStateException("Only an issued Demand Draft can be printed. Current status: " + leaflet.get("status"));
}
final int printCount = ((Number) leaflet.get("print_count")).intValue();
final LocalDateTime now = LocalDateTime.now(java.time.Clock.systemDefaultZone());
if (printCount == 0) {
this.jdbcTemplate.update(
"UPDATE m_sponsor_dd_leaflet SET print_count = print_count + 1, last_printed_date = ? WHERE id = ?", now, leafletId);
return new CommandProcessingResultBuilder().withEntityId(leafletId).build();
}
final List<Map<String, Object>> approved = this.jdbcTemplate.queryForList(
"SELECT id FROM m_sponsor_dd_reprint_request WHERE leaflet_id = ? AND status = 'APPROVED' AND consumed = false "
+ "ORDER BY id DESC LIMIT 1", leafletId);
if (approved.isEmpty()) {
throw new IllegalStateException(
"This Demand Draft has already been printed. Submit a reprint request for manager approval before printing again.");
}
final long reprintRequestId = ((Number) approved.get(0).get("id")).longValue();
this.jdbcTemplate.update("UPDATE m_sponsor_dd_reprint_request SET consumed = true WHERE id = ?", reprintRequestId);
this.jdbcTemplate.update(
"UPDATE m_sponsor_dd_leaflet SET print_count = print_count + 1, last_printed_date = ? WHERE id = ?", now, leafletId);
return new CommandProcessingResultBuilder().withEntityId(leafletId).withSubEntityId(reprintRequestId).build();
}
@Override
@Transactional
public CommandProcessingResult requestReprint(final Long leafletId, final String reason) {
final AppUser currentUser = this.context.authenticatedUser();
final Map<String, Object> leaflet = findLeafletOrThrow(leafletId);
if (!"ISSUED".equals(leaflet.get("status"))) {
throw new IllegalStateException("Only an issued Demand Draft can be reprinted. Current status: " + leaflet.get("status"));
}
final int printCount = ((Number) leaflet.get("print_count")).intValue();
if (printCount == 0) {
throw new IllegalStateException("This Demand Draft has not been printed yet - use Print, not Request Reprint.");
}
if (reason == null || reason.isBlank()) {
throw new IllegalArgumentException("A reason is required to request a reprint.");
}
if (!this.jdbcTemplate.queryForList(
"SELECT id FROM m_sponsor_dd_reprint_request WHERE leaflet_id = ? AND status = 'PENDING'", leafletId).isEmpty()) {
throw new IllegalStateException(
"A reprint request is already pending for this Demand Draft. Wait for it to be approved or rejected.");
}
if (!this.jdbcTemplate.queryForList(
"SELECT id FROM m_sponsor_dd_reprint_request WHERE leaflet_id = ? AND status = 'APPROVED' AND consumed = false",
leafletId).isEmpty()) {
throw new IllegalStateException("A reprint has already been approved for this Demand Draft - use Print to consume it.");
}
final KeyHolder kh = new GeneratedKeyHolder();
final LocalDateTime now = LocalDateTime.now(java.time.Clock.systemDefaultZone());
this.jdbcTemplate.update(con -> {
final java.sql.PreparedStatement ps = con.prepareStatement(
"INSERT INTO m_sponsor_dd_reprint_request (leaflet_id, reason, status, maker_id, created_date) "
+ "VALUES (?, ?, ?, ?, ?)",
new String[] { "id" });
ps.setLong(1, leafletId);
ps.setString(2, reason);
ps.setString(3, STATUS_PENDING);
ps.setLong(4, currentUser.getId());
ps.setObject(5, now);
return ps;
}, kh);
final long newId = Objects.requireNonNull(kh.getKey()).longValue();
return new CommandProcessingResultBuilder().withEntityId(newId).build();
}
@Override
@Transactional
public CommandProcessingResult approveReprintRequest(final Long reprintRequestId) {
final AppUser currentUser = this.context.authenticatedUser();
validateCheckerRole(currentUser);
final Map<String, Object> request = findReprintRequestOrThrow(reprintRequestId);
if (!STATUS_PENDING.equals(request.get("status"))) {
throw new IllegalStateException("Reprint request is not pending. Current status: " + request.get("status"));
}
final Long makerId = ((Number) request.get("maker_id")).longValue();
if (makerId.equals(currentUser.getId()) && !currentUser.hasAnyPermission("ALL_FUNCTIONS")) {
throw new IllegalStateException("You cannot approve your own reprint request.");
}
final LocalDateTime now = LocalDateTime.now(java.time.Clock.systemDefaultZone());
this.jdbcTemplate.update(
"UPDATE m_sponsor_dd_reprint_request SET status = ?, checker_id = ?, checker_date = ? WHERE id = ?",
STATUS_APPROVED, currentUser.getId(), now, reprintRequestId);
return new CommandProcessingResultBuilder().withEntityId(reprintRequestId).build();
}
@Override
@Transactional
public CommandProcessingResult rejectReprintRequest(final Long reprintRequestId, final String rejectionReason) {
final AppUser currentUser = this.context.authenticatedUser();
validateCheckerRole(currentUser);
final Map<String, Object> request = findReprintRequestOrThrow(reprintRequestId);
if (!STATUS_PENDING.equals(request.get("status"))) {
throw new IllegalStateException("Reprint request is not pending. Current status: " + request.get("status"));
}
final Long makerId = ((Number) request.get("maker_id")).longValue();
if (makerId.equals(currentUser.getId()) && !currentUser.hasAnyPermission("ALL_FUNCTIONS")) {
throw new IllegalStateException("You cannot reject your own reprint request.");
}
final LocalDateTime now = LocalDateTime.now(java.time.Clock.systemDefaultZone());
this.jdbcTemplate.update(
"UPDATE m_sponsor_dd_reprint_request SET status = ?, checker_id = ?, checker_date = ?, rejection_reason = ? WHERE id = ?",
STATUS_REJECTED, currentUser.getId(), now, rejectionReason, reprintRequestId);
return new CommandProcessingResultBuilder().withEntityId(reprintRequestId).build();
}
private Map<String, Object> findLeafletOrThrow(final Long leafletId) {
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(
"SELECT id, status, print_count FROM m_sponsor_dd_leaflet WHERE id = ?", leafletId);
if (rows.isEmpty()) {
throw new IllegalArgumentException("Demand Draft leaflet not found.");
}
return rows.get(0);
}
private Map<String, Object> findReprintRequestOrThrow(final Long reprintRequestId) {
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(
"SELECT * FROM m_sponsor_dd_reprint_request WHERE id = ?", reprintRequestId);
if (rows.isEmpty()) {
throw new IllegalArgumentException("Sponsor DD reprint request not found.");
}
return rows.get(0);
}
private boolean isCheckerRole(final AppUser user) {
if (user.hasAnyPermission("ALL_FUNCTIONS")) return true;
return user.getRoles().stream()
.map(r -> r.getName() == null ? "" : r.getName().toLowerCase().trim())
.anyMatch(n -> n.contains("manager") || n.contains("head cashier"));
}
private void validateCheckerRole(final AppUser user) {
if (!isCheckerRole(user)) {
throw new IllegalStateException("Only a Bank Manager (or Head Cashier) can approve or reject reprint requests.");
}
}
}
@@ -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.sponsordd.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface SponsorDDWritePlatformService {
CommandProcessingResult registerInventory(Long sponsorBankId, Long branchId, Long seriesStart, Long seriesEnd, LocalDate receivedDate);
/**
* clientId may be null for a walk-in purchaser who isn't a bank client (cash payment) — in that
* case walkinPurchaserName/Address/AadhaarMasked identify the purchaser instead.
*/
CommandProcessingResult issueDemandDraft(Long leafletId, Long clientId, String walkinPurchaserName,
String walkinPurchaserAddress, String walkinPurchaserAadhaarMasked, String beneficiaryName, BigDecimal faceValue,
Long issuingBranchId, LocalDate issuedDate, Long savingsTransactionId, Long currentTransactionId, Long journalEntryId,
String payableAtBranch, String payableAtCity);
CommandProcessingResult cancelDemandDraft(Long leafletId, String remarks, Long refundSavingsTransactionId, Long refundCurrentTransactionId);
CommandProcessingResult stopPaymentDemandDraft(Long leafletId, String remarks);
CommandProcessingResult sendAdvice(Long sponsorBankId);
}
@@ -0,0 +1,403 @@
/**
* 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.sponsordd.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBank;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorBankRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDAuditLog;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDAuditLogRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDInventory;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDInventoryRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDLeaflet;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDLeafletRepository;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDTransaction;
import org.apache.fineract.portfolio.sponsordd.domain.SponsorDDTransactionRepository;
import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SponsorDDWritePlatformServiceImpl implements SponsorDDWritePlatformService {
private final PlatformSecurityContext context;
private final SponsorDDInventoryRepository inventoryRepository;
private final SponsorDDLeafletRepository leafletRepository;
private final SponsorDDTransactionRepository transactionRepository;
private final SponsorDDAuditLogRepository auditLogRepository;
private final SponsorBankRepository sponsorBankRepository;
private final OfficeRepositoryWrapper officeRepositoryWrapper;
private final ClientRepositoryWrapper clientRepositoryWrapper;
private final SavingsAccountTransactionRepository savingsTransactionRepository;
private final CurrentAccountTransactionRepository currentTransactionRepository;
private final JournalEntryRepository journalEntryRepository;
private final JdbcTemplate jdbcTemplate;
@Autowired
public SponsorDDWritePlatformServiceImpl(
final PlatformSecurityContext context,
final SponsorDDInventoryRepository inventoryRepository,
final SponsorDDLeafletRepository leafletRepository,
final SponsorDDTransactionRepository transactionRepository,
final SponsorDDAuditLogRepository auditLogRepository,
final SponsorBankRepository sponsorBankRepository,
final OfficeRepositoryWrapper officeRepositoryWrapper,
final ClientRepositoryWrapper clientRepositoryWrapper,
final SavingsAccountTransactionRepository savingsTransactionRepository,
final CurrentAccountTransactionRepository currentTransactionRepository,
final JournalEntryRepository journalEntryRepository,
final JdbcTemplate jdbcTemplate) {
this.context = context;
this.inventoryRepository = inventoryRepository;
this.leafletRepository = leafletRepository;
this.transactionRepository = transactionRepository;
this.auditLogRepository = auditLogRepository;
this.sponsorBankRepository = sponsorBankRepository;
this.officeRepositoryWrapper = officeRepositoryWrapper;
this.clientRepositoryWrapper = clientRepositoryWrapper;
this.savingsTransactionRepository = savingsTransactionRepository;
this.currentTransactionRepository = currentTransactionRepository;
this.journalEntryRepository = journalEntryRepository;
this.jdbcTemplate = jdbcTemplate;
}
@Override
@Transactional
public CommandProcessingResult registerInventory(final Long sponsorBankId, final Long branchId, final Long seriesStart,
final Long seriesEnd, final LocalDate receivedDate) {
final AppUser currentUser = this.context.authenticatedUser();
if (seriesStart > seriesEnd) {
throw new IllegalArgumentException("Series start number must be less than or equal to series end number.");
}
final SponsorBank sponsorBank = this.sponsorBankRepository.findById(sponsorBankId)
.orElseThrow(() -> new IllegalArgumentException("Sponsor Bank not found with ID: " + sponsorBankId));
final Office branch = this.officeRepositoryWrapper.findOneWithNotFoundDetection(branchId);
final int totalLeaves = (int) (seriesEnd - seriesStart) + 1;
final SponsorDDInventory inventory = new SponsorDDInventory(
sponsorBank,
branch,
seriesStart,
seriesEnd,
totalLeaves,
receivedDate,
"ACTIVE",
currentUser,
LocalDateTime.now(),
null,
null
);
this.inventoryRepository.saveAndFlush(inventory);
// Pre-create leaflets in bulk
final List<SponsorDDLeaflet> leaflets = new ArrayList<>();
for (long currentVal = seriesStart; currentVal <= seriesEnd; currentVal++) {
final String ddNumber = String.valueOf(currentVal);
final SponsorDDLeaflet leaflet = new SponsorDDLeaflet(
inventory,
ddNumber,
"AVAILABLE",
BigDecimal.ZERO,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
"NOT_SENT",
null,
1,
currentUser,
LocalDateTime.now(),
null,
null
);
leaflets.add(leaflet);
}
this.leafletRepository.saveAllAndFlush(leaflets);
// Audit the creation of each leaflet
final List<SponsorDDAuditLog> auditLogs = new ArrayList<>();
for (final SponsorDDLeaflet leaflet : leaflets) {
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"STOCK_RECEIPT",
null,
"AVAILABLE",
currentUser,
"127.0.0.1",
null,
"Draft leaflet registered into inventory.",
LocalDateTime.now()
);
auditLogs.add(auditLog);
}
this.auditLogRepository.saveAll(auditLogs);
return new CommandProcessingResultBuilder()
.withEntityId(inventory.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult issueDemandDraft(final Long leafletId, final Long clientId, final String walkinPurchaserName,
final String walkinPurchaserAddress, final String walkinPurchaserAadhaarMasked, final String beneficiaryName,
final BigDecimal faceValue, final Long issuingBranchId, final LocalDate issuedDate,
final Long savingsTransactionId, final Long currentTransactionId, final Long journalEntryId,
final String payableAtBranch, final String payableAtCity) {
final AppUser currentUser = this.context.authenticatedUser();
final SponsorDDLeaflet leaflet = this.leafletRepository.findById(leafletId)
.orElseThrow(() -> new IllegalArgumentException("Demand Draft leaflet not found."));
if (!"AVAILABLE".equals(leaflet.getStatus())) {
throw new IllegalStateException("Demand Draft leaflet is not available. Current status: " + leaflet.getStatus());
}
final Client client = clientId != null ? this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId) : null;
final Office branch = this.officeRepositoryWrapper.findOneWithNotFoundDetection(issuingBranchId);
// Find linked financial items
SavingsAccountTransaction savingsTxn = null;
if (savingsTransactionId != null) {
savingsTxn = this.savingsTransactionRepository.findById(savingsTransactionId)
.orElseThrow(() -> new IllegalArgumentException("Savings transaction not found."));
}
CurrentAccountTransaction currentTxn = null;
if (currentTransactionId != null) {
currentTxn = this.currentTransactionRepository.findById(currentTransactionId)
.orElseThrow(() -> new IllegalArgumentException("Current account transaction not found."));
}
JournalEntry journalEntry = null;
if (journalEntryId != null) {
journalEntry = this.journalEntryRepository.findById(journalEntryId)
.orElseThrow(() -> new IllegalArgumentException("Journal Entry not found."));
}
final String preStatus = leaflet.getStatus();
leaflet.setStatus("ISSUED");
leaflet.setFaceValue(faceValue);
leaflet.setIssuedToClient(client);
leaflet.setWalkinPurchaserName(walkinPurchaserName);
leaflet.setWalkinPurchaserAddress(walkinPurchaserAddress);
leaflet.setWalkinPurchaserAadhaarMasked(walkinPurchaserAadhaarMasked);
leaflet.setBeneficiaryName(beneficiaryName);
leaflet.setPayableAtBranch(payableAtBranch);
leaflet.setPayableAtCity(payableAtCity);
leaflet.setIssuingBranch(branch);
leaflet.setIssuedDate(issuedDate);
leaflet.setAdviceStatus("NOT_SENT");
leaflet.setLastModifiedBy(currentUser);
leaflet.setLastModifiedDate(LocalDateTime.now());
this.leafletRepository.saveAndFlush(leaflet);
// Link transaction
final SponsorDDTransaction ddTransaction = new SponsorDDTransaction(
leaflet,
savingsTxn,
currentTxn,
journalEntry,
"ISSUANCE",
faceValue,
LocalDateTime.now()
);
this.transactionRepository.save(ddTransaction);
// Audit Log
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"ISSUED",
preStatus,
"ISSUED",
currentUser,
"127.0.0.1",
null,
"Demand draft issued to customer.",
LocalDateTime.now()
);
this.auditLogRepository.save(auditLog);
return new CommandProcessingResultBuilder()
.withEntityId(leaflet.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult cancelDemandDraft(final Long leafletId, final String remarks,
final Long refundSavingsTransactionId, final Long refundCurrentTransactionId) {
final AppUser currentUser = this.context.authenticatedUser();
final SponsorDDLeaflet leaflet = this.leafletRepository.findById(leafletId)
.orElseThrow(() -> new IllegalArgumentException("Demand Draft leaflet not found."));
if (!"ISSUED".equals(leaflet.getStatus())) {
throw new IllegalStateException("Only ISSUED Demand Drafts can be cancelled. Current status: " + leaflet.getStatus());
}
SavingsAccountTransaction savingsTxn = null;
if (refundSavingsTransactionId != null) {
savingsTxn = this.savingsTransactionRepository.findById(refundSavingsTransactionId)
.orElseThrow(() -> new IllegalArgumentException("Refund savings transaction not found."));
}
CurrentAccountTransaction currentTxn = null;
if (refundCurrentTransactionId != null) {
currentTxn = this.currentTransactionRepository.findById(refundCurrentTransactionId)
.orElseThrow(() -> new IllegalArgumentException("Refund current account transaction not found."));
}
final String preStatus = leaflet.getStatus();
leaflet.setStatus("CANCELLED");
leaflet.setLastModifiedBy(currentUser);
leaflet.setLastModifiedDate(LocalDateTime.now());
this.leafletRepository.saveAndFlush(leaflet);
// Link refund transaction
final SponsorDDTransaction ddTransaction = new SponsorDDTransaction(
leaflet,
savingsTxn,
currentTxn,
null,
"CANCELLATION",
leaflet.getFaceValue(),
LocalDateTime.now()
);
this.transactionRepository.save(ddTransaction);
// Audit Log
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"CANCELLED",
preStatus,
"CANCELLED",
currentUser,
"127.0.0.1",
null,
remarks != null ? remarks : "Demand draft cancelled and refunded.",
LocalDateTime.now()
);
this.auditLogRepository.save(auditLog);
return new CommandProcessingResultBuilder()
.withEntityId(leaflet.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult stopPaymentDemandDraft(final Long leafletId, final String remarks) {
final AppUser currentUser = this.context.authenticatedUser();
final SponsorDDLeaflet leaflet = this.leafletRepository.findById(leafletId)
.orElseThrow(() -> new IllegalArgumentException("Demand Draft leaflet not found."));
if (!"ISSUED".equals(leaflet.getStatus())) {
throw new IllegalStateException("Only ISSUED Demand Drafts can be stopped. Current status: " + leaflet.getStatus());
}
final String preStatus = leaflet.getStatus();
leaflet.setStatus("STOPPED");
leaflet.setLastModifiedBy(currentUser);
leaflet.setLastModifiedDate(LocalDateTime.now());
this.leafletRepository.saveAndFlush(leaflet);
// Link stop transaction
final SponsorDDTransaction ddTransaction = new SponsorDDTransaction(
leaflet,
null,
null,
null,
"STOP_PAYMENT",
leaflet.getFaceValue(),
LocalDateTime.now()
);
this.transactionRepository.save(ddTransaction);
// Audit Log
final SponsorDDAuditLog auditLog = new SponsorDDAuditLog(
leaflet,
"STOP_PAYMENT",
preStatus,
"STOPPED",
currentUser,
"127.0.0.1",
null,
remarks != null ? remarks : "Stop payment instruction processed.",
LocalDateTime.now()
);
this.auditLogRepository.save(auditLog);
return new CommandProcessingResultBuilder()
.withEntityId(leaflet.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult sendAdvice(final Long sponsorBankId) {
final AppUser currentUser = this.context.authenticatedUser();
final LocalDateTime now = LocalDateTime.now();
// Update m_sponsor_dd_leaflet for the sponsor bank where status is ISSUED and advice_status is NOT_SENT
final String sql = "UPDATE m_sponsor_dd_leaflet leaf "
+ "SET advice_status = 'SENT', advice_sent_date = ?, lastmodifiedby_id = ?, lastmodified_date = ? "
+ "WHERE leaf.status = 'ISSUED' AND leaf.advice_status = 'NOT_SENT' "
+ "AND leaf.inventory_id IN (SELECT id FROM m_sponsor_dd_inventory WHERE sponsor_bank_id = ?)";
this.jdbcTemplate.update(sql, now, currentUser.getId(), now, sponsorBankId);
return new CommandProcessingResultBuilder()
.withEntityId(sponsorBankId)
.build();
}
}
@@ -370,4 +370,20 @@
<include file="parts/0340_add_terminate_true_to_savings_and_current_account.xml" relativeToChangelogFile="true"/>
<include file="parts/0341_add_current_account_pending_edit_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0342_add_current_account_edit_approval_permissions.xml" relativeToChangelogFile="true"/>
<include file="parts/0343_create_sponsor_bank_dd_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0344_create_sponsor_dd_issue_request_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0345_add_sponsor_dd_payable_gl_account_sbi.xml" relativeToChangelogFile="true"/>
<include file="parts/0347_add_sponsor_dd_walkin_cash_support.xml" relativeToChangelogFile="true"/>
<include file="parts/0348_add_sponsor_dd_cash_charges_gl_mapping.xml" relativeToChangelogFile="true"/>
<include file="parts/0349_replace_payment_type_with_sponsor_bank.xml" relativeToChangelogFile="true"/>
<include file="parts/0350_add_sponsor_dd_cash_receipt_suspense.xml" relativeToChangelogFile="true"/>
<include file="parts/0351_add_sponsor_dd_reprint_approval.xml" relativeToChangelogFile="true"/>
<include file="parts/0352_add_sponsor_dd_payable_at.xml" relativeToChangelogFile="true"/>
<include file="parts/0353_add_sponsor_dd_gst_on_charges.xml" relativeToChangelogFile="true"/>
<include file="parts/0354_add_sponsor_bank_identification_codes.xml" relativeToChangelogFile="true"/>
<include file="parts/0355_add_sponsor_dd_cashier_deposit_link.xml" relativeToChangelogFile="true"/>
<include file="parts/0356_add_dd_breakdown_to_cashier_deposit_request.xml" relativeToChangelogFile="true"/>
<include file="parts/0357_add_dd_limit_to_sponsor_dd_gl_mapping.xml" relativeToChangelogFile="true"/>
<include file="parts/0358_add_batch_id_to_sponsor_dd_issue_request.xml" relativeToChangelogFile="true"/>
<include file="parts/0359_reclassify_dd_gst_payable_as_income.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Per sponsor-bank GL account configuration: which GL account is credited when a DD is
issued (liability owed to the sponsor bank), and which suspense GL account is debited
when the customer pays by cheque instead of an account debit. -->
<changeSet author="fineract" id="0344-1-create-gl-mapping">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="m_sponsor_dd_gl_mapping" />
</not>
</preConditions>
<createTable tableName="m_sponsor_dd_gl_mapping">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="payment_type_id" type="BIGINT">
<constraints nullable="false" unique="true" uniqueConstraintName="sdd_glmap_unq_payment_type"
foreignKeyName="fk_sdd_glmap_payment_type_id" references="m_payment_type(id)" />
</column>
<column name="dd_payable_gl_account_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_glmap_payable_gl_id" references="acc_gl_account(id)" />
</column>
<column name="cheque_clearing_gl_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_glmap_clearing_gl_id" references="acc_gl_account(id)" />
</column>
<column name="created_date" type="DATETIME" defaultValueComputed="now()">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
<!-- Maker-checker queue: clerk submits a PENDING request when a customer wants a DD issued
(either by debiting their own savings/current account, or by handing over a cheque);
a manager/head-cashier approval performs the actual GL posting and flips the leaflet
to ISSUED via the existing SponsorDDWritePlatformService.issueDemandDraft(). -->
<changeSet author="fineract" id="0344-2-create-issue-request">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="m_sponsor_dd_issue_request" />
</not>
</preConditions>
<createTable tableName="m_sponsor_dd_issue_request">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="leaflet_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_issuereq_leaflet_id" references="m_sponsor_dd_leaflet(id)" />
</column>
<column name="client_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_issuereq_client_id" references="m_client(id)" />
</column>
<column name="beneficiary_name" type="VARCHAR(150)">
<constraints nullable="false" />
</column>
<column name="face_value" type="DECIMAL(19,6)">
<constraints nullable="false" />
</column>
<column name="issuing_branch_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_issuereq_branch_id" references="m_office(id)" />
</column>
<column name="issued_date" type="DATE">
<constraints nullable="false" />
</column>
<!-- ACCOUNT_DEBIT or CHEQUE -->
<column name="payment_method" type="VARCHAR(20)">
<constraints nullable="false" />
</column>
<column name="savings_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_savings_id" references="m_savings_account(id)" />
</column>
<column name="current_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_current_id" references="m_current_account(id)" />
</column>
<column name="cheque_number" type="VARCHAR(30)">
<constraints nullable="true" />
</column>
<column name="cheque_bank_name" type="VARCHAR(100)">
<constraints nullable="true" />
</column>
<!-- PENDING, APPROVED, REJECTED -->
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
<constraints nullable="false" />
</column>
<column name="maker_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_issuereq_maker_id" references="m_appuser(id)" />
</column>
<column name="created_date" type="DATETIME" defaultValueComputed="now()">
<constraints nullable="false" />
</column>
<column name="checker_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_checker_id" references="m_appuser(id)" />
</column>
<column name="checker_date" type="DATETIME">
<constraints nullable="true" />
</column>
<column name="rejection_reason" type="VARCHAR(255)">
<constraints nullable="true" />
</column>
<column name="resulting_savings_transaction_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_result_savings_txn" references="m_savings_account_transaction(id)" />
</column>
<column name="resulting_current_transaction_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_result_current_txn" references="m_current_account_transaction(id)" />
</column>
<column name="resulting_journal_entry_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_result_journal" references="acc_gl_journal_entry(id)" />
</column>
</createTable>
</changeSet>
<changeSet author="fineract" id="0344-3-index-issue-request-status">
<preConditions onFail="MARK_RAN">
<not>
<indexExists tableName="m_sponsor_dd_issue_request" indexName="idx_sdd_issuereq_status" />
</not>
</preConditions>
<createIndex indexName="idx_sdd_issuereq_status" tableName="m_sponsor_dd_issue_request">
<column name="status" />
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Dedicated liability GL account for the Sponsor DD Payable balance owed to SBI, as a child
of the existing "OTHER LIABILITY" account (gl_code 9), matching the pattern already used
by "INTEREST PAYABLE" (gl_code 9.2). -->
<changeSet author="fineract" id="0345-1-insert-sponsor-dd-payable-sbi-gl-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.3'</sqlCheck>
</not>
</preConditions>
<insert tableName="acc_gl_account">
<column name="name" value="Sponsor DD Payable - SBI"/>
<column name="gl_code" value="9.3"/>
<column name="parent_id" valueNumeric="22"/>
<column name="hierarchy" value="."/>
<column name="account_usage" valueNumeric="1"/>
<column name="classification_enum" valueNumeric="2"/>
<column name="disabled" valueBoolean="false"/>
<column name="manual_journal_entries_allowed" valueBoolean="true"/>
<column name="description" value="Liability owed to SBI for sponsor bank demand drafts issued on their behalf, pending clearing."/>
</insert>
</changeSet>
<!-- Fineract's GL hierarchy path embeds the row's own auto-generated id, so it can only be
set after the insert above. -->
<changeSet author="fineract" id="0345-2-fix-sponsor-dd-payable-sbi-hierarchy">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.3' and hierarchy = '.'</sqlCheck>
</preConditions>
<sql>
UPDATE acc_gl_account SET hierarchy = CONCAT('.', id, '.') WHERE gl_code = '9.3' AND hierarchy = '.';
</sql>
</changeSet>
<!-- Wire the new GL account into the Sponsor DD GL mapping for the SBI payment type, so
DD issuance requests for SBI can be approved without manual configuration. -->
<changeSet author="fineract" id="0345-3-map-sponsor-dd-payable-sbi">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">
select count(*) from m_sponsor_dd_gl_mapping m join m_payment_type pt on pt.id = m.payment_type_id where pt.value = 'SBI'
</sqlCheck>
</preConditions>
<sql>
INSERT INTO m_sponsor_dd_gl_mapping (payment_type_id, dd_payable_gl_account_id, created_date)
SELECT pt.id, gl.id, CURRENT_TIMESTAMP
FROM m_payment_type pt, acc_gl_account gl
WHERE pt.value = 'SBI' AND gl.gl_code = '9.3';
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Support DD issuance to a walk-in person paying cash who is not a bank client at all
(not just a different-branch customer). client_id becomes optional; when absent, the
walk-in purchaser's own KYC details are captured instead. Aadhaar is stored masked only
(last 4 digits) per UIDAI/RBI guidance — the full number is never persisted. -->
<changeSet author="fineract" id="0347-1-issue-request-client-id-nullable">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_sponsor_dd_issue_request" columnName="client_id" />
</preConditions>
<dropNotNullConstraint tableName="m_sponsor_dd_issue_request" columnName="client_id" columnDataType="BIGINT" />
</changeSet>
<changeSet author="fineract" id="0347-2-issue-request-add-walkin-fields">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="walkin_purchaser_name" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="walkin_purchaser_name" type="VARCHAR(150)">
<constraints nullable="true" />
</column>
<column name="walkin_purchaser_address" type="VARCHAR(255)">
<constraints nullable="true" />
</column>
<column name="walkin_purchaser_aadhaar_masked" type="VARCHAR(20)">
<constraints nullable="true" />
</column>
<column name="charges_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
<constraints nullable="false" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0347-3-leaflet-add-walkin-fields">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_sponsor_dd_leaflet" columnName="walkin_purchaser_name" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_leaflet">
<column name="walkin_purchaser_name" type="VARCHAR(150)">
<constraints nullable="true" />
</column>
<column name="walkin_purchaser_address" type="VARCHAR(255)">
<constraints nullable="true" />
</column>
<column name="walkin_purchaser_aadhaar_masked" type="VARCHAR(20)">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
<!-- GL accounts for a CASH payment: Dr Cash-in-Hand (face value + charges) / Cr Sponsor DD
Payable (face value) / Cr Charges Income (charges, if any). Both optional — CASH issuance
is blocked with a clear error until these are configured, matching the existing pattern
for cheque_clearing_gl_account_id. -->
<changeSet author="fineract" id="0347-4-gl-mapping-add-cash-and-charges-accounts">
<preConditions onFail="MARK_RAN">
<not>
<columnExists tableName="m_sponsor_dd_gl_mapping" columnName="cash_gl_account_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_gl_mapping">
<column name="cash_gl_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_glmap_cash_gl_id" references="acc_gl_account(id)" />
</column>
<column name="charges_income_gl_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_glmap_charges_gl_id" references="acc_gl_account(id)" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Dedicated income GL account for charges/commission collected on sponsor bank DD issuance,
as a child of the existing "INCOME HEAD" account (gl_code 10), matching the pattern
already used by "INCOME FROM LOAN" (gl_code 10.1) / "INCOME FROM ALL" (gl_code 10.2). -->
<changeSet author="fineract" id="0348-1-insert-dd-charges-income-gl-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '10.3'</sqlCheck>
</not>
</preConditions>
<insert tableName="acc_gl_account">
<column name="name" value="DD Issuance Charges Income"/>
<column name="gl_code" value="10.3"/>
<column name="parent_id" valueNumeric="25"/>
<column name="hierarchy" value="."/>
<column name="account_usage" valueNumeric="1"/>
<column name="classification_enum" valueNumeric="4"/>
<column name="disabled" valueBoolean="false"/>
<column name="manual_journal_entries_allowed" valueBoolean="true"/>
<column name="description" value="Commission/charges income collected from customers and walk-in purchasers when issuing a sponsor bank demand draft."/>
</insert>
</changeSet>
<!-- Fineract's GL hierarchy path embeds the row's own auto-generated id, so it can only be
set after the insert above. -->
<changeSet author="fineract" id="0348-2-fix-dd-charges-income-hierarchy">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '10.3' and hierarchy = '.'</sqlCheck>
</preConditions>
<sql>
UPDATE acc_gl_account SET hierarchy = CONCAT('.', id, '.') WHERE gl_code = '10.3' AND hierarchy = '.';
</sql>
</changeSet>
<!-- Wire "Cash at Tellers" (the standard Fineract GL account for cash physically received at
the teller counter, gl_code 2) and the new charges income account into the existing
Sponsor DD GL mapping row(s), so CASH-method walk-in DD issuance can be approved without
further manual configuration. -->
<changeSet author="fineract" id="0348-3-map-dd-cash-and-charges-accounts">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="0">
select count(*) from m_sponsor_dd_gl_mapping where cash_gl_account_id is null or charges_income_gl_account_id is null
</sqlCheck>
</not>
</preConditions>
<sql>
UPDATE m_sponsor_dd_gl_mapping
SET cash_gl_account_id = (select id from acc_gl_account where gl_code = '2'),
charges_income_gl_account_id = (select id from acc_gl_account where gl_code = '10.3')
WHERE cash_gl_account_id is null or charges_income_gl_account_id is null;
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<changeSet author="fineract" id="0349-0-drop-old-sponsor-bank-table">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_bank" />
<not>
<columnExists tableName="m_sponsor_bank" columnName="createdby_id" />
</not>
</preConditions>
<sql>
DROP TABLE IF EXISTS m_sponsor_bank CASCADE;
</sql>
</changeSet>
<changeSet author="fineract" id="0349-1-create-sponsor-bank-table-v3">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="m_sponsor_bank" />
</not>
</preConditions>
<createTable tableName="m_sponsor_bank">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false" unique="true" uniqueConstraintName="sdd_bank_unq_name" />
</column>
<column name="description" type="VARCHAR(255)">
<constraints nullable="true" />
</column>
<column name="createdby_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_bank_maker_id" references="m_appuser(id)" />
</column>
<column name="created_date" type="DATETIME" defaultValueComputed="now()">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
<changeSet author="fineract" id="0349-2-migrate-existing-payment-types">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_bank" />
<tableExists tableName="m_payment_type" />
</preConditions>
<sql>
-- Insert unique sponsor banks from payment types into m_sponsor_bank
INSERT INTO m_sponsor_bank (name, description, createdby_id, created_date)
SELECT DISTINCT pt.value, pt.description, 1, CURRENT_TIMESTAMP
FROM m_payment_type pt
WHERE (pt.description = 'Sponsor Bank'
OR pt.id IN (SELECT DISTINCT payment_type_id FROM m_sponsor_dd_inventory))
AND pt.value NOT IN (SELECT name FROM m_sponsor_bank);
</sql>
</changeSet>
<changeSet author="fineract" id="0349-3-add-sponsor-bank-id-to-inventory">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_inventory" />
<not>
<columnExists tableName="m_sponsor_dd_inventory" columnName="sponsor_bank_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_inventory">
<column name="sponsor_bank_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_inv_sponsor_bank_id" references="m_sponsor_bank(id)" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0349-4-update-inventory-sponsor-bank-ids">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_sponsor_dd_inventory" columnName="sponsor_bank_id" />
<columnExists tableName="m_sponsor_dd_inventory" columnName="payment_type_id" />
</preConditions>
<sql>
-- Update sponsor_bank_id based on name matching from m_payment_type
UPDATE m_sponsor_dd_inventory
SET sponsor_bank_id = (
SELECT sb.id
FROM m_sponsor_bank sb
JOIN m_payment_type pt ON pt.value = sb.name
WHERE pt.id = m_sponsor_dd_inventory.payment_type_id
LIMIT 1
)
WHERE sponsor_bank_id IS NULL;
</sql>
</changeSet>
<changeSet author="fineract" id="0349-5-set-sponsor-bank-id-not-null">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_sponsor_dd_inventory" columnName="sponsor_bank_id" />
</preConditions>
<addNotNullConstraint tableName="m_sponsor_dd_inventory" columnName="sponsor_bank_id" columnDataType="BIGINT" />
</changeSet>
<changeSet author="fineract" id="0349-6-drop-payment-type-from-inventory">
<preConditions onFail="MARK_RAN">
<columnExists tableName="m_sponsor_dd_inventory" columnName="payment_type_id" />
</preConditions>
<dropForeignKeyConstraint baseTableName="m_sponsor_dd_inventory" constraintName="fk_sdd_inv_payment_type_id" />
<dropColumn tableName="m_sponsor_dd_inventory" columnName="payment_type_id" />
</changeSet>
<changeSet author="fineract" id="0349-7-decouple-gl-mapping-payment-type">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_gl_mapping" />
<columnExists tableName="m_sponsor_dd_gl_mapping" columnName="payment_type_id" />
</preConditions>
<dropForeignKeyConstraint baseTableName="m_sponsor_dd_gl_mapping" constraintName="fk_sdd_glmap_payment_type_id" />
<dropUniqueConstraint tableName="m_sponsor_dd_gl_mapping" constraintName="sdd_glmap_unq_payment_type" />
<dropColumn tableName="m_sponsor_dd_gl_mapping" columnName="payment_type_id" />
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Interim liability GL account for cash physically received from a walk-in DD purchaser at
submission time, before the manager has approved the request. Real-world equivalent of
"advance received against demand draft application" - keeps the cash movement auditable
immediately (the teller's cash-in-hand balance is correct as soon as cash is taken),
without prematurely crediting the Sponsor DD Payable liability, which is only actually
owed to the sponsor bank once the draft is approved/issued. -->
<changeSet author="fineract" id="0350-1-insert-dd-cash-suspense-gl-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.4'</sqlCheck>
</not>
</preConditions>
<insert tableName="acc_gl_account">
<column name="name" value="DD Cash Received - Pending Approval"/>
<column name="gl_code" value="9.4"/>
<column name="parent_id" valueNumeric="22"/>
<column name="hierarchy" value="."/>
<column name="account_usage" valueNumeric="1"/>
<column name="classification_enum" valueNumeric="2"/>
<column name="disabled" valueBoolean="false"/>
<column name="manual_journal_entries_allowed" valueBoolean="true"/>
<column name="description" value="Interim liability for cash physically received from a walk-in DD purchaser at submission time, pending manager approval of the issuance request. Cleared into Sponsor DD Payable + Charges Income on approval, or reversed back to Cash at Tellers on rejection."/>
</insert>
</changeSet>
<changeSet author="fineract" id="0350-2-fix-dd-cash-suspense-hierarchy">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.4' and hierarchy = '.'</sqlCheck>
</preConditions>
<sql>
UPDATE acc_gl_account SET hierarchy = CONCAT('.', id, '.') WHERE gl_code = '9.4' AND hierarchy = '.';
</sql>
</changeSet>
<changeSet author="fineract" id="0350-3-add-cash-suspense-to-gl-mapping">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_gl_mapping" />
<not>
<columnExists tableName="m_sponsor_dd_gl_mapping" columnName="cash_suspense_gl_account_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_gl_mapping">
<column name="cash_suspense_gl_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_glmap_cash_suspense_gl_id" references="acc_gl_account(id)" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0350-4-map-dd-cash-suspense-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="0">
select count(*) from m_sponsor_dd_gl_mapping where cash_suspense_gl_account_id is null
</sqlCheck>
</not>
</preConditions>
<sql>
UPDATE m_sponsor_dd_gl_mapping
SET cash_suspense_gl_account_id = (select id from acc_gl_account where gl_code = '9.4')
WHERE cash_suspense_gl_account_id is null;
</sql>
</changeSet>
<!-- Audit trail: which journal entry recorded the initial cash receipt (posted at submission),
and which one reversed it (posted only if the request is later rejected). Both are nullable
since ACCOUNT_DEBIT/CHEQUE requests never populate them, and an approved CASH request only
ever gets a receipt entry (never a refund one). -->
<changeSet author="fineract" id="0350-5-add-cash-receipt-journal-entry-column">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_issue_request" />
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="cash_receipt_journal_entry_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="cash_receipt_journal_entry_id" type="BIGINT">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0350-6-add-cash-refund-journal-entry-column">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_issue_request" />
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="cash_refund_journal_entry_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="cash_refund_journal_entry_id" type="BIGINT">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- A DD leaflet is a negotiable instrument - printing it a second time is a control risk
(duplicate physical drafts). The first print is free; any print after that requires a
reprint request approved by a higher authority (maker-checker), same pattern as issuance. -->
<changeSet author="fineract" id="0351-1-add-print-tracking-to-leaflet">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_leaflet" />
<not>
<columnExists tableName="m_sponsor_dd_leaflet" columnName="print_count" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_leaflet">
<column name="print_count" type="INT" defaultValueNumeric="0">
<constraints nullable="false" />
</column>
<column name="last_printed_date" type="DATETIME">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0351-2-create-reprint-request-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="m_sponsor_dd_reprint_request" />
</not>
</preConditions>
<createTable tableName="m_sponsor_dd_reprint_request">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="leaflet_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_reprint_leaflet_id" references="m_sponsor_dd_leaflet(id)" />
</column>
<column name="reason" type="VARCHAR(500)">
<constraints nullable="false" />
</column>
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
<constraints nullable="false" />
</column>
<column name="consumed" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false" />
</column>
<column name="maker_id" type="BIGINT">
<constraints nullable="false" foreignKeyName="fk_sdd_reprint_maker_id" references="m_appuser(id)" />
</column>
<column name="created_date" type="DATETIME" defaultValueComputed="now()">
<constraints nullable="false" />
</column>
<column name="checker_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_reprint_checker_id" references="m_appuser(id)" />
</column>
<column name="checker_date" type="DATETIME">
<constraints nullable="true" />
</column>
<column name="rejection_reason" type="VARCHAR(1000)">
<constraints nullable="true" />
</column>
</createTable>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- "Payable at [Branch], [City]" - standard field on a demand draft telling the beneficiary
where it can be encashed. Nullable at the DB level (existing rows have neither); the
write service enforces it as required for new submissions. -->
<changeSet author="fineract" id="0352-1-add-payable-at-to-issue-request">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_issue_request" />
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="payable_at_branch" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="payable_at_branch" type="VARCHAR(150)">
<constraints nullable="true" />
</column>
<column name="payable_at_city" type="VARCHAR(100)">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0352-2-add-payable-at-to-leaflet">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_leaflet" />
<not>
<columnExists tableName="m_sponsor_dd_leaflet" columnName="payable_at_branch" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_leaflet">
<column name="payable_at_branch" type="VARCHAR(150)">
<constraints nullable="true" />
</column>
<column name="payable_at_city" type="VARCHAR(100)">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- GST collected on DD issuance charges is money owed to the government, not bank income -
it gets its own liability GL account, separate from Charges Income. -->
<changeSet author="fineract" id="0353-1-insert-gst-payable-gl-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.5'</sqlCheck>
</not>
</preConditions>
<insert tableName="acc_gl_account">
<column name="name" value="GST Payable on DD Charges"/>
<column name="gl_code" value="9.5"/>
<column name="parent_id" valueNumeric="22"/>
<column name="hierarchy" value="."/>
<column name="account_usage" valueNumeric="1"/>
<column name="classification_enum" valueNumeric="2"/>
<column name="disabled" valueBoolean="false"/>
<column name="manual_journal_entries_allowed" valueBoolean="true"/>
<column name="description" value="GST collected from customers/purchasers on Sponsor DD issuance charges, held pending remittance to the government - not bank income."/>
</insert>
</changeSet>
<changeSet author="fineract" id="0353-2-fix-gst-payable-hierarchy">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.5' and hierarchy = '.'</sqlCheck>
</preConditions>
<sql>
UPDATE acc_gl_account SET hierarchy = CONCAT('.', id, '.') WHERE gl_code = '9.5' AND hierarchy = '.';
</sql>
</changeSet>
<!-- gst_percentage is deliberately a configurable column, not a hardcoded constant in Java -
it can be changed at any time (e.g. when the government revises the GST rate) via the
existing GL mapping admin endpoint, without a code deployment. Defaults to 18.00 (the
current GST rate on Indian banking service charges) for existing/new rows. -->
<changeSet author="fineract" id="0353-3-add-gst-columns-to-gl-mapping">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_gl_mapping" />
<not>
<columnExists tableName="m_sponsor_dd_gl_mapping" columnName="gst_percentage" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_gl_mapping">
<column name="gst_percentage" type="DECIMAL(5,2)" defaultValueNumeric="18.00">
<constraints nullable="false" />
</column>
<column name="gst_payable_gl_account_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_glmap_gst_gl_id" references="acc_gl_account(id)" />
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0353-4-map-gst-payable-account">
<preConditions onFail="MARK_RAN">
<not>
<sqlCheck expectedResult="0">
select count(*) from m_sponsor_dd_gl_mapping where gst_payable_gl_account_id is null
</sqlCheck>
</not>
</preConditions>
<sql>
UPDATE m_sponsor_dd_gl_mapping
SET gst_payable_gl_account_id = (select id from acc_gl_account where gl_code = '9.5')
WHERE gst_payable_gl_account_id is null;
</sql>
</changeSet>
<!-- Locked in at submission time (not recomputed at approval), so a rate change between
submission and approval never silently alters a request already in flight. -->
<changeSet author="fineract" id="0353-5-add-gst-columns-to-issue-request">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_issue_request" />
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="gst_amount" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="gst_amount" type="DECIMAL(19,6)" defaultValueNumeric="0">
<constraints nullable="false" />
</column>
<column name="gst_percentage_applied" type="DECIMAL(5,2)" defaultValueNumeric="0">
<constraints nullable="false" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Identification codes needed to route/reconcile settlement with the sponsor bank and
confirm its identity unambiguously - IFSC (settlement branch), RBI/NPCI bank code, and
SWIFT/BIC (only relevant if it's ever a foreign correspondent bank). All optional at the
DB level since existing rows have none, but the API validates IFSC format when supplied. -->
<changeSet author="fineract" id="0354-1-add-sponsor-bank-identification-codes">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_bank" />
<not>
<columnExists tableName="m_sponsor_bank" columnName="ifsc_code" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_bank">
<column name="ifsc_code" type="VARCHAR(11)">
<constraints nullable="true" />
</column>
<column name="bank_code" type="VARCHAR(20)">
<constraints nullable="true" />
</column>
<column name="swift_code" type="VARCHAR(11)">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- CASH DD requests no longer post their own "cash received" GL entry - the cashier records
the physical cash intake themselves via Transaction Entry (AC.TP=GL, target = the DD Cash
Suspense GL account), which is the existing, real cashier-position-tracked mechanism
(m_cashier_deposit_request + m_savings_txn_denomination). This column links a sponsor DD
issue request to the specific approved cashier deposit request that received its cash, so
we can validate the amount/account match and prevent the same cash being reused twice. -->
<changeSet author="fineract" id="0355-1-add-cashier-deposit-request-link">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_issue_request" />
<tableExists tableName="m_cashier_deposit_request" />
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="cashier_deposit_request_id" />
</not>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="cashier_deposit_request_id" type="BIGINT">
<constraints nullable="true" foreignKeyName="fk_sdd_issuereq_cashier_deposit_id"
references="m_cashier_deposit_request(id)" />
</column>
</addColumn>
</changeSet>
<!-- The cashier's Transaction Entry GL-deposit flow always contras cash against "Cash at Vault"
(gl_code 1), not "Cash at Teller" (gl_code 2) - see CashierDepositRequestApiResource's
executeGlJournalEntry / the frontend's loadCashVaultGl(). Repoint our cash_gl_account_id to
match, since the reject/refund leg (Dr DD Cash Suspense / Cr this account) must reverse
against the same account the cashier's approved deposit actually touched. -->
<changeSet author="fineract" id="0355-2-repoint-cash-account-to-vault">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_sponsor_dd_gl_mapping" />
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '1'</sqlCheck>
</preConditions>
<sql>
UPDATE m_sponsor_dd_gl_mapping
SET cash_gl_account_id = (select id from acc_gl_account where gl_code = '1')
WHERE cash_gl_account_id = (select id from acc_gl_account where gl_code = '2');
</sql>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Transaction Entry's DD + Cash + GL row splits the single deposit amount into DD face value,
issuance charges and GST on those charges (Amount + Charges + GST, rounded up to the target
cash-collected total). Today m_cashier_deposit_request only stores that combined "amount" -
the breakdown is lost once the request is submitted, forcing whoever later issues the actual
DD leaflet in the Sponsor DD module to retype face value/charges from scratch. These columns
persist the breakdown at submit time (null for ordinary, non-DD deposit requests) so the DD
issuance screen can read it back via the linked cashier_deposit_request_id and pre-fill its
form instead of retyping. -->
<changeSet author="fineract" id="0356-1-add-dd-breakdown-to-cashier-deposit-request">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_cashier_deposit_request" />
<not>
<columnExists tableName="m_cashier_deposit_request" columnName="dd_face_value" />
</not>
</preConditions>
<addColumn tableName="m_cashier_deposit_request">
<column name="dd_face_value" type="DECIMAL(19,6)" />
<column name="dd_charges" type="DECIMAL(19,6)" />
<column name="dd_gst_percentage" type="DECIMAL(19,6)" />
<column name="dd_gst_amount" type="DECIMAL(19,6)" />
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Admin-configurable ceiling for a single DD's face value, also used as the ceiling for the
combined face value of all DDs issued to the same beneficiary on the same day (structuring
control for walk-in/large-cash DD issuance). Null/unset means no limit is enforced. -->
<changeSet author="fineract" id="0357-1-add-dd-limit-amount">
<preConditions onFail="MARK_RAN">
<and>
<tableExists tableName="m_sponsor_dd_gl_mapping"/>
<not>
<columnExists tableName="m_sponsor_dd_gl_mapping" columnName="dd_limit_amount"/>
</not>
</and>
</preConditions>
<addColumn tableName="m_sponsor_dd_gl_mapping">
<column name="dd_limit_amount" type="DECIMAL(19,6)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Groups several DD issue requests submitted together as one "DD slip" (one customer account
debited/one cheque, several drafts for different beneficiaries/amounts). Set to the id of the
first row inserted for the slip - every row in the slip, including that first row, carries the
same batch_id, so the group can be queried/approved/rejected as one unit. Null for an ordinary
single-DD request (unchanged behaviour). No FK - it's a grouping tag, not a relationship to a
separate header row. -->
<changeSet author="fineract" id="0358-1-add-batch-id">
<preConditions onFail="MARK_RAN">
<and>
<tableExists tableName="m_sponsor_dd_issue_request"/>
<not>
<columnExists tableName="m_sponsor_dd_issue_request" columnName="batch_id"/>
</not>
</and>
</preConditions>
<addColumn tableName="m_sponsor_dd_issue_request">
<column name="batch_id" type="BIGINT">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
<changeSet author="fineract" id="0358-2-index-batch-id">
<preConditions onFail="MARK_RAN">
<and>
<tableExists tableName="m_sponsor_dd_issue_request"/>
<not>
<indexExists tableName="m_sponsor_dd_issue_request" indexName="idx_sdd_issue_req_batch_id"/>
</not>
</and>
</preConditions>
<createIndex tableName="m_sponsor_dd_issue_request" indexName="idx_sdd_issue_req_batch_id">
<column name="batch_id"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
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">
<!-- Bank's own choice: book GST collected on DD charges as income rather than tracking it as a
payable owed to the government. Same GL account (gl_code 9.5), same id, so
m_sponsor_dd_gl_mapping.gst_payable_gl_account_id and the DD-issuance GL posting code need no
change at all - this only re-tags how the account is classified/grouped for reporting.
parent_id=25 matches the parent already used by "DD Issuance Charges Income" (gl_code 10.3,
see 0348), so both DD-related income accounts group together under the same income parent. -->
<changeSet author="fineract" id="0359-1-reclassify-gst-payable-as-income">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">select count(*) from acc_gl_account where gl_code = '9.5' and classification_enum = 2</sqlCheck>
</preConditions>
<sql>
UPDATE acc_gl_account
SET classification_enum = 4, parent_id = 25
WHERE gl_code = '9.5';
</sql>
</changeSet>
</databaseChangeLog>