Merge branch 'dev' of https://git.lincpay.tech/CBSNexora/cbsnexora-backend into dev
This commit is contained in:
+39
-39
@@ -1,40 +1,40 @@
|
|||||||
*.class
|
*.class
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
.gradle
|
.gradle
|
||||||
build
|
build
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
*.swp
|
*.swp
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
catalina.base_IS_UNDEFINED/
|
catalina.base_IS_UNDEFINED/
|
||||||
keystore.jks
|
keystore.jks
|
||||||
bin/
|
bin/
|
||||||
.lock
|
.lock
|
||||||
fineract-provider/out/
|
fineract-provider/out/
|
||||||
fineract-provider/config/swagger/config.json
|
fineract-provider/config/swagger/config.json
|
||||||
fineract-provider/config/swagger/fineract-input.yaml
|
fineract-provider/config/swagger/fineract-input.yaml
|
||||||
licenses
|
licenses
|
||||||
**/.asciidoctor/images/*
|
**/.asciidoctor/images/*
|
||||||
**/.asciidoctor/diagram/*
|
**/.asciidoctor/diagram/*
|
||||||
**/images/diag-*.svg
|
**/images/diag-*.svg
|
||||||
|
|
||||||
fineract-provider/src/main/generated/
|
fineract-provider/src/main/generated/
|
||||||
|
|
||||||
**/out/
|
**/out/
|
||||||
gradleExp/
|
gradleExp/
|
||||||
|
|
||||||
.run/
|
.run/
|
||||||
.java-version
|
.java-version
|
||||||
|
|
||||||
.windsurf/
|
.windsurf/
|
||||||
|
|
||||||
# Application logs
|
# Application logs
|
||||||
fineract-provider/logs/
|
fineract-provider/logs/
|
||||||
-1
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.accounting.journalentry;
|
package org.apache.fineract.accounting.journalentry;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
||||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||||
|
|||||||
+14
-14
@@ -277,14 +277,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
|
final JsonArray chargeToIncomeAccountMappingArray = this.fromApiJsonHelper.extractJsonArrayNamed(arrayFragmentName, element);
|
||||||
/**
|
/*
|
||||||
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
|
* Variable stores a map representation of charges (key) and their associated income Id's (value) extracted from
|
||||||
* the passed in Jsoncommand
|
* the passed in Jsoncommand
|
||||||
**/
|
*/
|
||||||
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
|
final Map<Long, Long> inputChargeToIncomeAccountMap = new HashMap<>();
|
||||||
/***
|
/*
|
||||||
* Variable stores all charges which have already been mapped to Income Accounts in the system
|
* Variable stores all charges which have already been mapped to Income Accounts in the system
|
||||||
**/
|
*/
|
||||||
final Set<Long> existingCharges = new HashSet<>();
|
final Set<Long> existingCharges = new HashSet<>();
|
||||||
if (chargeToIncomeAccountMappingArray != null) {
|
if (chargeToIncomeAccountMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
@@ -301,13 +301,13 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// If input map is empty, delete all existing mappings
|
// If input map is empty, delete all existing mappings
|
||||||
if (inputChargeToIncomeAccountMap.size() == 0) {
|
if (inputChargeToIncomeAccountMap.size() == 0) {
|
||||||
this.accountMappingRepository.deleteAll(existingChargeToIncomeAccountMappings);
|
this.accountMappingRepository.deleteAll(existingChargeToIncomeAccountMappings);
|
||||||
} /**
|
} /*
|
||||||
* Else, <br/>
|
* Else, <br/>
|
||||||
* update existing mappings OR <br/>
|
* update existing mappings OR <br/>
|
||||||
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
|
* delete old mappings (which are already present, but not passed in as a part of Jsoncommand)<br/>
|
||||||
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
|
* Create new mappings for charges that are passed in as a part of the Jsoncommand but not already present
|
||||||
*
|
*
|
||||||
**/
|
*/
|
||||||
else {
|
else {
|
||||||
for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
|
for (final ProductToGLAccountMapping chargeToIncomeAccountMapping : existingChargeToIncomeAccountMappings) {
|
||||||
final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
|
final Long currentCharge = chargeToIncomeAccountMapping.getCharge().getId();
|
||||||
@@ -356,14 +356,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
.findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
|
.findAllPaymentTypeToFundSourceMappings(productId, portfolioProductType.getValue());
|
||||||
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
|
final JsonArray paymentChannelMappingArray = this.fromApiJsonHelper
|
||||||
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
|
.extractJsonArrayNamed(LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element);
|
||||||
/**
|
/*
|
||||||
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
|
* Variable stores a map representation of Payment channels (key) and their fund sources (value) extracted from
|
||||||
* the passed in Jsoncommand
|
* the passed in Jsoncommand
|
||||||
**/
|
*/
|
||||||
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
|
final Map<Long, Long> inputPaymentChannelFundSourceMap = new HashMap<>();
|
||||||
/***
|
/*
|
||||||
* Variable stores all payment types which have already been mapped to Fund Sources in the system
|
* Variable stores all payment types which have already been mapped to Fund Sources in the system
|
||||||
**/
|
*/
|
||||||
final Set<Long> existingPaymentTypes = new HashSet<>();
|
final Set<Long> existingPaymentTypes = new HashSet<>();
|
||||||
if (paymentChannelMappingArray != null) {
|
if (paymentChannelMappingArray != null) {
|
||||||
if (changes != null) {
|
if (changes != null) {
|
||||||
@@ -381,14 +381,14 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// If input map is empty, delete all existing mappings
|
// If input map is empty, delete all existing mappings
|
||||||
if (inputPaymentChannelFundSourceMap.isEmpty()) {
|
if (inputPaymentChannelFundSourceMap.isEmpty()) {
|
||||||
this.accountMappingRepository.deleteAll(existingPaymentChannelToFundSourceMappings);
|
this.accountMappingRepository.deleteAll(existingPaymentChannelToFundSourceMappings);
|
||||||
} /**
|
} /*
|
||||||
* Else, <br/>
|
* Else, <br/>
|
||||||
* update existing mappings OR <br/>
|
* update existing mappings OR <br/>
|
||||||
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
|
* delete old mappings (which re already present, but not passed in as a part of Jsoncommand)<br/>
|
||||||
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
|
* Create new mappings for payment types that are passed in as a part of the Jsoncommand but not already
|
||||||
* present
|
* present
|
||||||
*
|
*
|
||||||
**/
|
*/
|
||||||
else {
|
else {
|
||||||
for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
|
for (final ProductToGLAccountMapping existingPaymentChannelToFundSourceMapping : existingPaymentChannelToFundSourceMappings) {
|
||||||
final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
|
final Long currentPaymentChannelId = existingPaymentChannelToFundSourceMapping.getPaymentType().getId();
|
||||||
@@ -584,9 +584,9 @@ public class ProductToGLAccountMappingHelper {
|
|||||||
// based on input condition
|
// based on input condition
|
||||||
|
|
||||||
GLAccount glAccount;
|
GLAccount glAccount;
|
||||||
/**
|
/*
|
||||||
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
|
* Both CASH and Accrual placeholders have the same value for income from Interest and penalties
|
||||||
**/
|
*/
|
||||||
CashAccountsForLoan placeHolderAccountType;
|
CashAccountsForLoan placeHolderAccountType;
|
||||||
if (isPenalty) {
|
if (isPenalty) {
|
||||||
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
|
glAccount = getAccountByIdAndType(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), GLAccountType.INCOME,
|
||||||
|
|||||||
+2
-2
@@ -181,12 +181,12 @@ public class TellerApiResource {
|
|||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Operation(summary = "Create Cashiers", description = """
|
@Operation(summary = "Create Cashiers", description = """
|
||||||
Mandatory Fields:\s
|
Mandatory Fields:
|
||||||
Cashier/staff, Fromm Date, To Date, Full Day or From time and To time
|
Cashier/staff, Fromm Date, To Date, Full Day or From time and To time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Optional Fields:\s
|
Optional Fields:
|
||||||
Description/Notes""")
|
Description/Notes""")
|
||||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersRequest.class)))
|
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersRequest.class)))
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersResponse.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = TellerApiResourceSwagger.PostTellersTellerIdCashiersResponse.class)))
|
||||||
|
|||||||
+8
@@ -640,6 +640,14 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createUserAgreement() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "USERAGREEMENT";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/useragreements";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createOffice() {
|
public CommandWrapperBuilder createOffice() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "OFFICE";
|
this.entityName = "OFFICE";
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.infrastructure.documentmanagement.adapter;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decouples the entity "current signature" pointer from the generic document service, the same
|
||||||
|
* way {@link EntityImageIdAdapter} decouples the "current photo" pointer — lets
|
||||||
|
* DocumentWritePlatformServiceImpl (fineract-document module) give single-slot upsert semantics to
|
||||||
|
* signature uploads without depending on portfolio-module entities directly.
|
||||||
|
*/
|
||||||
|
public interface EntitySignatureIdAdapter {
|
||||||
|
|
||||||
|
/** @param documentName the m_document.name of the document being created/removed */
|
||||||
|
boolean accept(String entityType, String documentName);
|
||||||
|
|
||||||
|
Optional<SignatureIdResult> get(Long entityId);
|
||||||
|
|
||||||
|
Optional<SignatureIdResult> set(Long entityId, Long signatureId);
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
class SignatureIdResult implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulator-side audit log for CFCFRMS (I4C/MHA) events.
|
* Simulator-side audit log for CFCFRMS (I4C/MHA) events.
|
||||||
@@ -69,7 +70,7 @@ public class CfcfrmsSimulatorLog extends AbstractAuditableWithUTCDateTimeCustom<
|
|||||||
this.eventType = eventType;
|
this.eventType = eventType;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.responsePayload = responsePayload;
|
this.responsePayload = responsePayload;
|
||||||
this.eventTimestamp = OffsetDateTime.now();
|
this.eventTimestamp = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CfcfrmsSimulatorLog(final String cfcfrmsRef, final String ticketId, final String bankIfsc,
|
public CfcfrmsSimulatorLog(final String cfcfrmsRef, final String ticketId, final String bankIfsc,
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audit trail for every status change on a CFCFRMS complaint ticket.
|
* Audit trail for every status change on a CFCFRMS complaint ticket.
|
||||||
@@ -54,7 +55,7 @@ public class ComplaintAuditLog extends AbstractAuditableWithUTCDateTimeCustom<Lo
|
|||||||
this.event = event;
|
this.event = event;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.eventTimestamp = OffsetDateTime.now();
|
this.eventTimestamp = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComplaintAuditLog(final String ticketId, final String event, final String description,
|
public ComplaintAuditLog(final String ticketId, final String event, final String description,
|
||||||
|
|||||||
+3
-2
@@ -26,6 +26,7 @@ import jakarta.persistence.Table;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CFCFRMS Complaint Ticket — represents a cyber fraud complaint pushed by MHA/I4C to the bank.
|
* CFCFRMS Complaint Ticket — represents a cyber fraud complaint pushed by MHA/I4C to the bank.
|
||||||
@@ -121,7 +122,7 @@ public class ComplaintTicket extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
ticket.status = ComplaintStatus.RECEIVED;
|
ticket.status = ComplaintStatus.RECEIVED;
|
||||||
ticket.assignedOfficer = assignedOfficer;
|
ticket.assignedOfficer = assignedOfficer;
|
||||||
ticket.statePoliceRef = statePoliceRef;
|
ticket.statePoliceRef = statePoliceRef;
|
||||||
ticket.receivedAt = OffsetDateTime.now();
|
ticket.receivedAt = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
return ticket;
|
return ticket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ public class ComplaintTicket extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
this.status = newStatus;
|
this.status = newStatus;
|
||||||
this.actionTaken = actionNote;
|
this.actionTaken = actionNote;
|
||||||
if (newStatus == ComplaintStatus.RESOLVED || newStatus == ComplaintStatus.REJECTED) {
|
if (newStatus == ComplaintStatus.RESOLVED || newStatus == ComplaintStatus.REJECTED) {
|
||||||
this.resolvedAt = OffsetDateTime.now();
|
this.resolvedAt = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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.client.adapter;
|
||||||
|
|
||||||
|
import static java.util.Objects.nonNull;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntitySignatureIdAdapter;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepository;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
class ClientSignatureIdAdapter implements EntitySignatureIdAdapter {
|
||||||
|
|
||||||
|
private static final String ENTITY_TYPE = "clients";
|
||||||
|
private static final String DOCUMENT_NAME = "clientSignature";
|
||||||
|
|
||||||
|
private final ClientRepository repository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(String entityType, String documentName) {
|
||||||
|
return ENTITY_TYPE.equalsIgnoreCase(entityType) && DOCUMENT_NAME.equalsIgnoreCase(documentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Optional<SignatureIdResult> get(Long entityId) {
|
||||||
|
return repository.findById(entityId).filter(client -> nonNull(client.getSignatureId()))
|
||||||
|
.map(client -> SignatureIdResult.builder().id(client.getSignatureId()).displayName(client.getDisplayName()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Optional<SignatureIdResult> set(Long entityId, Long signatureId) {
|
||||||
|
final var result = get(entityId);
|
||||||
|
|
||||||
|
if (signatureId == null) {
|
||||||
|
repository.removeSignatureId(entityId);
|
||||||
|
} else {
|
||||||
|
repository.updateByIdAndSignatureId(entityId, signatureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-2
@@ -63,6 +63,7 @@ public class ClientApiConstants {
|
|||||||
public static final String CLIENT_CLASSIFICATION = "ClientClassification";
|
public static final String CLIENT_CLASSIFICATION = "ClientClassification";
|
||||||
|
|
||||||
public static final String OCCUPATION_CATEGORY = "OccupationCategory";
|
public static final String OCCUPATION_CATEGORY = "OccupationCategory";
|
||||||
|
public static final String PWD_CATEGORY = "PwdCategory";
|
||||||
public static final String OCCUPATION = "Occupation";
|
public static final String OCCUPATION = "Occupation";
|
||||||
|
|
||||||
public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
|
public static final String CLIENT_NON_PERSON_CONSTITUTION = "Constitution";
|
||||||
@@ -96,6 +97,15 @@ public class ClientApiConstants {
|
|||||||
public static final String mobileNoParamName = "mobileNo";
|
public static final String mobileNoParamName = "mobileNo";
|
||||||
public static final String alternateMobileNoParamName = "alternateMobileNo";
|
public static final String alternateMobileNoParamName = "alternateMobileNo";
|
||||||
public static final String emailAddressParamName = "emailAddress";
|
public static final String emailAddressParamName = "emailAddress";
|
||||||
|
// Dual-OTP contact-change verification (see ClientContactOtpService) — required only when
|
||||||
|
// mobileNo or emailAddress is being changed via PUT /clients/{id}. Reference an already
|
||||||
|
// VERIFIED m_otp_log row for the OLD destination (current DB value) and the NEW destination
|
||||||
|
// (incoming value) respectively. Field-specific (not a single generic pair) so mobileNo and
|
||||||
|
// emailAddress can both change in the same request without ambiguity.
|
||||||
|
public static final String mobileOldOtpIdParamName = "mobileOldOtpId";
|
||||||
|
public static final String mobileNewOtpIdParamName = "mobileNewOtpId";
|
||||||
|
public static final String emailOldOtpIdParamName = "emailOldOtpId";
|
||||||
|
public static final String emailNewOtpIdParamName = "emailNewOtpId";
|
||||||
public static final String firstnameParamName = "firstname";
|
public static final String firstnameParamName = "firstname";
|
||||||
public static final String middlenameParamName = "middlename";
|
public static final String middlenameParamName = "middlename";
|
||||||
public static final String lastnameParamName = "lastname";
|
public static final String lastnameParamName = "lastname";
|
||||||
@@ -138,7 +148,13 @@ public class ClientApiConstants {
|
|||||||
public static final String religionParamName = "religion";
|
public static final String religionParamName = "religion";
|
||||||
public static final String socialCategoryParamName = "socialCategory";
|
public static final String socialCategoryParamName = "socialCategory";
|
||||||
public static final String monthlyIncomeParamName = "monthlyIncome";
|
public static final String monthlyIncomeParamName = "monthlyIncome";
|
||||||
|
public static final String annualIncomeParamName = "annualIncome";
|
||||||
public static final String annualTurnoverParamName = "annualTurnover";
|
public static final String annualTurnoverParamName = "annualTurnover";
|
||||||
|
public static final String monthlyTurnoverParamName = "monthlyTurnover";
|
||||||
|
public static final String isSeniorCitizenParamName = "isSeniorCitizen";
|
||||||
|
public static final String isPwdParamName = "isPwd";
|
||||||
|
public static final String pwdCategoryIdParamName = "pwdCategoryId";
|
||||||
|
public static final String pwdCategoryParamName = "pwdCategory";
|
||||||
public static final String agricultureIncomeParamName = "agricultureIncome";
|
public static final String agricultureIncomeParamName = "agricultureIncome";
|
||||||
public static final String nonAgricultureIncomeParamName = "nonAgricultureIncome";
|
public static final String nonAgricultureIncomeParamName = "nonAgricultureIncome";
|
||||||
public static final String ageYearsParamName = "ageYears";
|
public static final String ageYearsParamName = "ageYears";
|
||||||
@@ -248,8 +264,9 @@ public class ClientApiConstants {
|
|||||||
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
|
staffOptionsParamName, dateOfBirthParamName, genderParamName, clientTypeParamName, clientClassificationParamName,
|
||||||
salutationParamName, occupationCategoryParamName, occupationParamName, maritalStatusParamName,
|
salutationParamName, occupationCategoryParamName, occupationParamName, maritalStatusParamName,
|
||||||
educationLevelParamName, degreeQualificationParamName,
|
educationLevelParamName, degreeQualificationParamName,
|
||||||
religionParamName, socialCategoryParamName, monthlyIncomeParamName,
|
religionParamName, socialCategoryParamName, monthlyIncomeParamName, annualIncomeParamName,
|
||||||
annualTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
annualTurnoverParamName, monthlyTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||||
|
isSeniorCitizenParamName, isPwdParamName, pwdCategoryParamName,
|
||||||
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
||||||
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianClientDisplayNameParamName, guardianRelationParamName,
|
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianClientDisplayNameParamName, guardianRelationParamName,
|
||||||
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
||||||
|
|||||||
@@ -82,7 +82,13 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
private CodeValueData religion;
|
private CodeValueData religion;
|
||||||
private CodeValueData socialCategory;
|
private CodeValueData socialCategory;
|
||||||
private BigDecimal monthlyIncome;
|
private BigDecimal monthlyIncome;
|
||||||
|
private BigDecimal annualIncome;
|
||||||
private BigDecimal annualTurnover;
|
private BigDecimal annualTurnover;
|
||||||
|
private BigDecimal monthlyTurnover;
|
||||||
|
private Boolean isSeniorCitizen;
|
||||||
|
private Boolean isPwd;
|
||||||
|
private CodeValueData pwdCategory;
|
||||||
|
private Collection<CodeValueData> pwdCategoryOptions;
|
||||||
private BigDecimal agricultureIncome;
|
private BigDecimal agricultureIncome;
|
||||||
private BigDecimal nonAgricultureIncome;
|
private BigDecimal nonAgricultureIncome;
|
||||||
private Integer ageYears;
|
private Integer ageYears;
|
||||||
@@ -400,13 +406,19 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.degreeQualificationOptions = templateData.degreeQualificationOptions;
|
data.degreeQualificationOptions = templateData.degreeQualificationOptions;
|
||||||
data.religionOptions = templateData.religionOptions;
|
data.religionOptions = templateData.religionOptions;
|
||||||
data.socialCategoryOptions = templateData.socialCategoryOptions;
|
data.socialCategoryOptions = templateData.socialCategoryOptions;
|
||||||
|
data.pwdCategoryOptions = templateData.pwdCategoryOptions;
|
||||||
data.maritalStatus = clientData.maritalStatus;
|
data.maritalStatus = clientData.maritalStatus;
|
||||||
data.educationLevel = clientData.educationLevel;
|
data.educationLevel = clientData.educationLevel;
|
||||||
data.degreeQualification = clientData.degreeQualification;
|
data.degreeQualification = clientData.degreeQualification;
|
||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
@@ -456,13 +468,19 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.degreeQualificationOptions = clientData.degreeQualificationOptions;
|
data.degreeQualificationOptions = clientData.degreeQualificationOptions;
|
||||||
data.religionOptions = clientData.religionOptions;
|
data.religionOptions = clientData.religionOptions;
|
||||||
data.socialCategoryOptions = clientData.socialCategoryOptions;
|
data.socialCategoryOptions = clientData.socialCategoryOptions;
|
||||||
|
data.pwdCategoryOptions = clientData.pwdCategoryOptions;
|
||||||
data.maritalStatus = clientData.maritalStatus;
|
data.maritalStatus = clientData.maritalStatus;
|
||||||
data.educationLevel = clientData.educationLevel;
|
data.educationLevel = clientData.educationLevel;
|
||||||
data.degreeQualification = clientData.degreeQualification;
|
data.degreeQualification = clientData.degreeQualification;
|
||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
@@ -509,7 +527,12 @@ public final class ClientData implements Comparable<ClientData>, Serializable {
|
|||||||
data.religion = clientData.religion;
|
data.religion = clientData.religion;
|
||||||
data.socialCategory = clientData.socialCategory;
|
data.socialCategory = clientData.socialCategory;
|
||||||
data.monthlyIncome = clientData.monthlyIncome;
|
data.monthlyIncome = clientData.monthlyIncome;
|
||||||
|
data.annualIncome = clientData.annualIncome;
|
||||||
data.annualTurnover = clientData.annualTurnover;
|
data.annualTurnover = clientData.annualTurnover;
|
||||||
|
data.monthlyTurnover = clientData.monthlyTurnover;
|
||||||
|
data.isSeniorCitizen = clientData.isSeniorCitizen;
|
||||||
|
data.isPwd = clientData.isPwd;
|
||||||
|
data.pwdCategory = clientData.pwdCategory;
|
||||||
data.agricultureIncome = clientData.agricultureIncome;
|
data.agricultureIncome = clientData.agricultureIncome;
|
||||||
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
data.nonAgricultureIncome = clientData.nonAgricultureIncome;
|
||||||
data.ageYears = clientData.ageYears;
|
data.ageYears = clientData.ageYears;
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "image_id")
|
@Column(name = "image_id")
|
||||||
private Long imageId;
|
private Long imageId;
|
||||||
|
|
||||||
|
@Column(name = "signature_id")
|
||||||
|
private Long signatureId;
|
||||||
|
|
||||||
@Column(name = "status_enum", nullable = false)
|
@Column(name = "status_enum", nullable = false)
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
@@ -231,9 +234,25 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "monthly_income")
|
@Column(name = "monthly_income")
|
||||||
private java.math.BigDecimal monthlyIncome;
|
private java.math.BigDecimal monthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "annual_income")
|
||||||
|
private java.math.BigDecimal annualIncome;
|
||||||
|
|
||||||
@Column(name = "annual_turnover")
|
@Column(name = "annual_turnover")
|
||||||
private java.math.BigDecimal annualTurnover;
|
private java.math.BigDecimal annualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "monthly_turnover")
|
||||||
|
private java.math.BigDecimal monthlyTurnover;
|
||||||
|
|
||||||
|
@Column(name = "is_senior_citizen")
|
||||||
|
private Boolean isSeniorCitizen;
|
||||||
|
|
||||||
|
@Column(name = "is_pwd")
|
||||||
|
private Boolean isPwd;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "pwd_category_cv_id")
|
||||||
|
private CodeValue pwdCategory;
|
||||||
|
|
||||||
@Column(name = "agriculture_income")
|
@Column(name = "agriculture_income")
|
||||||
private java.math.BigDecimal agricultureIncome;
|
private java.math.BigDecimal agricultureIncome;
|
||||||
|
|
||||||
@@ -832,6 +851,14 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.socialCategory != null ? this.socialCategory.getId() : null;
|
return this.socialCategory != null ? this.socialCategory.getId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long pwdCategoryId() {
|
||||||
|
return this.pwdCategory != null ? this.pwdCategory.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePwdCategory(CodeValue pwdCategory) {
|
||||||
|
this.pwdCategory = pwdCategory;
|
||||||
|
}
|
||||||
|
|
||||||
public void updateSocialCategory(CodeValue socialCategory) {
|
public void updateSocialCategory(CodeValue socialCategory) {
|
||||||
this.socialCategory = socialCategory;
|
this.socialCategory = socialCategory;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -29,6 +29,7 @@ import lombok.Getter;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form 60 declaration as per Rule 114B of Income Tax Rules, 1962.
|
* Form 60 declaration as per Rule 114B of Income Tax Rules, 1962.
|
||||||
@@ -87,7 +88,7 @@ public class ClientForm60 extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
form.estimatedTotalIncome = command.bigDecimalValueOfParameterNamed("estimatedTotalIncome");
|
form.estimatedTotalIncome = command.bigDecimalValueOfParameterNamed("estimatedTotalIncome");
|
||||||
form.reasonForNoPan = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamed("reasonForNoPan"), null);
|
form.reasonForNoPan = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamed("reasonForNoPan"), null);
|
||||||
form.declaration = command.booleanPrimitiveValueOfParameterNamed("declaration");
|
form.declaration = command.booleanPrimitiveValueOfParameterNamed("declaration");
|
||||||
form.submittedOn = LocalDate.now();
|
form.submittedOn = DateUtils.getLocalDateOfTenant();
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-10
@@ -32,6 +32,7 @@ import java.util.Map;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a client's KYC (Know Your Customer) record as mandated by the Reserve Bank of India
|
* Represents a client's KYC (Know Your Customer) record as mandated by the Reserve Bank of India
|
||||||
@@ -260,7 +261,7 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.gstinVerified = false;
|
this.gstinVerified = false;
|
||||||
this.cinVerified = false;
|
this.cinVerified = false;
|
||||||
this.dinVerified = false;
|
this.dinVerified = false;
|
||||||
this.initiatedOn = LocalDate.now();
|
this.initiatedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
public static ClientKyc initiateKyc(final Client client, final JsonCommand command) {
|
||||||
@@ -456,13 +457,13 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public void markCkycUploaded(final String ckycNumber, final String batchId) {
|
public void markCkycUploaded(final String ckycNumber, final String batchId) {
|
||||||
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
||||||
this.ckycBatchId = StringUtils.defaultIfEmpty(batchId, null);
|
this.ckycBatchId = StringUtils.defaultIfEmpty(batchId, null);
|
||||||
this.ckycUploadedOn = LocalDate.now();
|
this.ckycUploadedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called after successful 3rd-party verification of all required documents. */
|
/** Called after successful 3rd-party verification of all required documents. */
|
||||||
public void markVerified(final String ckycNumber) {
|
public void markVerified(final String ckycNumber) {
|
||||||
this.kycStatus = ClientKycStatus.VERIFIED.getValue();
|
this.kycStatus = ClientKycStatus.VERIFIED.getValue();
|
||||||
this.verifiedOn = LocalDate.now();
|
this.verifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
this.ckycNumber = StringUtils.defaultIfEmpty(ckycNumber, null);
|
||||||
|
|
||||||
final KycRiskCategory risk = KycRiskCategory.fromInt(this.riskCategory);
|
final KycRiskCategory risk = KycRiskCategory.fromInt(this.riskCategory);
|
||||||
@@ -474,14 +475,14 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
/** Called when verification fails. */
|
/** Called when verification fails. */
|
||||||
public void markRejected(final String remarks) {
|
public void markRejected(final String remarks) {
|
||||||
this.kycStatus = ClientKycStatus.REJECTED.getValue();
|
this.kycStatus = ClientKycStatus.REJECTED.getValue();
|
||||||
this.rejectedOn = LocalDate.now();
|
this.rejectedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.remarks = StringUtils.defaultIfEmpty(remarks, this.remarks);
|
this.remarks = StringUtils.defaultIfEmpty(remarks, this.remarks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markPanVerified(final String fullName, final String firstName, final String lastName,
|
public void markPanVerified(final String fullName, final String firstName, final String lastName,
|
||||||
final String aadhaarSeedingStatus, final String category, final String status) {
|
final String aadhaarSeedingStatus, final String category, final String status) {
|
||||||
this.panVerified = true;
|
this.panVerified = true;
|
||||||
this.panVerifiedOn = LocalDate.now();
|
this.panVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.panFullName = StringUtils.defaultIfEmpty(fullName, null);
|
this.panFullName = StringUtils.defaultIfEmpty(fullName, null);
|
||||||
this.panFirstName = StringUtils.defaultIfEmpty(firstName, null);
|
this.panFirstName = StringUtils.defaultIfEmpty(firstName, null);
|
||||||
this.panLastName = StringUtils.defaultIfEmpty(lastName, null);
|
this.panLastName = StringUtils.defaultIfEmpty(lastName, null);
|
||||||
@@ -507,28 +508,28 @@ public class ClientKyc extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
public void markAadhaarVerified() {
|
public void markAadhaarVerified() {
|
||||||
this.aadhaarVerified = true;
|
this.aadhaarVerified = true;
|
||||||
this.aadhaarVerifiedOn = LocalDate.now();
|
this.aadhaarVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markGstinVerified() {
|
public void markGstinVerified() {
|
||||||
this.gstinVerified = true;
|
this.gstinVerified = true;
|
||||||
this.gstinVerifiedOn = LocalDate.now();
|
this.gstinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markCinVerified() {
|
public void markCinVerified() {
|
||||||
this.cinVerified = true;
|
this.cinVerified = true;
|
||||||
this.cinVerifiedOn = LocalDate.now();
|
this.cinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markDinVerified() {
|
public void markDinVerified() {
|
||||||
this.dinVerified = true;
|
this.dinVerified = true;
|
||||||
this.dinVerifiedOn = LocalDate.now();
|
this.dinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markDinVerified(final String dinNumber, final String mcaResponseJson) {
|
public void markDinVerified(final String dinNumber, final String mcaResponseJson) {
|
||||||
this.dinNumber = dinNumber;
|
this.dinNumber = dinNumber;
|
||||||
this.dinVerified = true;
|
this.dinVerified = true;
|
||||||
this.dinVerifiedOn = LocalDate.now();
|
this.dinVerifiedOn = DateUtils.getLocalDateOfTenant();
|
||||||
this.isDirector = true;
|
this.isDirector = true;
|
||||||
this.dinMcaResponse = mcaResponseJson;
|
this.dinMcaResponse = mcaResponseJson;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -55,6 +55,14 @@ public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecif
|
|||||||
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
|
||||||
void removeImageId(@Param("clientId") Long clientId);
|
void removeImageId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE Client client SET client.signatureId = :signatureId WHERE client.id = :clientId")
|
||||||
|
void updateByIdAndSignatureId(@Param("clientId") Long clientId, @Param("signatureId") Long signatureId);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("UPDATE Client client SET client.signatureId = null WHERE client.id = :clientId")
|
||||||
|
void removeSignatureId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
* Count clients whose cif_number begins with the given prefix. Used by the CIF number generator
|
||||||
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
* (TTYYYYNNNNNN format) to compute the next sequence within a given (type, year) bucket.
|
||||||
|
|||||||
+138
@@ -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.client.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_client_risk_rule")
|
||||||
|
public class ClientRiskRule {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "rule_name", nullable = false, length = 100)
|
||||||
|
private String ruleName;
|
||||||
|
|
||||||
|
@Column(name = "legal_form_id")
|
||||||
|
private Integer legalFormId;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "occupation_category_cv_id")
|
||||||
|
private CodeValue occupationCategory;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "occupation_cv_id")
|
||||||
|
private CodeValue occupation;
|
||||||
|
|
||||||
|
@Column(name = "min_monthly_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minMonthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "max_monthly_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxMonthlyIncome;
|
||||||
|
|
||||||
|
@Column(name = "min_annual_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minAnnualIncome;
|
||||||
|
|
||||||
|
@Column(name = "max_annual_income", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxAnnualIncome;
|
||||||
|
|
||||||
|
@Column(name = "min_annual_turnover", precision = 19, scale = 6)
|
||||||
|
private BigDecimal minAnnualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "max_annual_turnover", precision = 19, scale = 6)
|
||||||
|
private BigDecimal maxAnnualTurnover;
|
||||||
|
|
||||||
|
@Column(name = "risk_category", nullable = false)
|
||||||
|
private Integer riskCategory;
|
||||||
|
|
||||||
|
@Column(name = "reason", nullable = false, length = 255)
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
@Column(name = "order_position", nullable = false)
|
||||||
|
private Integer orderPosition;
|
||||||
|
|
||||||
|
protected ClientRiskRule() {}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRuleName() {
|
||||||
|
return ruleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLegalFormId() {
|
||||||
|
return legalFormId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeValue getOccupationCategory() {
|
||||||
|
return occupationCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CodeValue getOccupation() {
|
||||||
|
return occupation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinMonthlyIncome() {
|
||||||
|
return minMonthlyIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxMonthlyIncome() {
|
||||||
|
return maxMonthlyIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinAnnualIncome() {
|
||||||
|
return minAnnualIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxAnnualIncome() {
|
||||||
|
return maxAnnualIncome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMinAnnualTurnover() {
|
||||||
|
return minAnnualTurnover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getMaxAnnualTurnover() {
|
||||||
|
return maxAnnualTurnover;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRiskCategory() {
|
||||||
|
return riskCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOrderPosition() {
|
||||||
|
return orderPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -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.client.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
|
||||||
|
public interface ClientRiskRuleRepository extends JpaRepository<ClientRiskRule, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT r FROM ClientRiskRule r ORDER BY r.orderPosition ASC")
|
||||||
|
List<ClientRiskRule> findAllOrderedByOrderPosition();
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending current account Nominee/Digital-Feature edit
|
||||||
|
* (m_current_account_pending_edit), for the Approvals -> Pending Current Account Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CurrentAccountPendingEditData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long currentAccountId;
|
||||||
|
private String accountNo;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
private boolean facilityConfigSubmitted;
|
||||||
|
private boolean nomineeSubmitted;
|
||||||
|
private Map<String, Object> proposedFacilityConfig;
|
||||||
|
private Map<String, Object> proposedNominee;
|
||||||
|
|
||||||
|
public CurrentAccountPendingEditData() {}
|
||||||
|
}
|
||||||
+2
-1
@@ -24,6 +24,7 @@ import jakarta.persistence.Table;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity log for each DIP / MNRL mobile number lookup request.
|
* Activity log for each DIP / MNRL mobile number lookup request.
|
||||||
@@ -78,6 +79,6 @@ public class MobileLookupLog extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
this.rawResponse = rawResponse;
|
this.rawResponse = rawResponse;
|
||||||
this.lookupSource = lookupSource;
|
this.lookupSource = lookupSource;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.lookedUpOn = OffsetDateTime.now();
|
this.lookedUpOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -18,7 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.sanctions.domain;
|
package org.apache.fineract.portfolio.sanctions.domain;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -40,4 +42,11 @@ public interface SanctionsEntryRepository extends JpaRepository<SanctionsEntry,
|
|||||||
void deleteAllByActiveTrue();
|
void deleteAllByActiveTrue();
|
||||||
|
|
||||||
long countByActiveTrue();
|
long countByActiveTrue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp the active sanctions list was last loaded into the database — i.e. the data
|
||||||
|
* currency shown to callers so they know which list snapshot a screening ran against.
|
||||||
|
*/
|
||||||
|
@Query("SELECT MAX(s.createdDate) FROM SanctionsEntry s WHERE s.active = true")
|
||||||
|
Optional<OffsetDateTime> findListLastLoadedOn();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity log for each sanctions screening request.
|
* Activity log for each sanctions screening request.
|
||||||
@@ -73,7 +74,7 @@ public class SanctionsScreeningLog extends AbstractAuditableWithUTCDateTimeCusto
|
|||||||
this.screeningSource = screeningSource;
|
this.screeningSource = screeningSource;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
this.ipAddress = ipAddress;
|
this.ipAddress = ipAddress;
|
||||||
this.screenedOn = OffsetDateTime.now();
|
this.screenedOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Accessors ─────────────────────────────────────────────────────────────
|
// ── Accessors ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.savings.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read-side view of a single pending savings account Nominee/Digital-Feature edit
|
||||||
|
* (m_savings_account_pending_edit), for the Approvals -> Pending Savings Account Edits screen.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SavingsAccountPendingEditData implements Serializable {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long savingsAccountId;
|
||||||
|
private String accountNo;
|
||||||
|
private Long makerId;
|
||||||
|
private String makerUsername;
|
||||||
|
private Long checkerId;
|
||||||
|
private String checkerUsername;
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
private String status;
|
||||||
|
private String rejectReason;
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
private boolean facilityConfigSubmitted;
|
||||||
|
private boolean nomineeSubmitted;
|
||||||
|
private Map<String, Object> proposedFacilityConfig;
|
||||||
|
private Map<String, Object> proposedNominee;
|
||||||
|
private String depositAccountType;
|
||||||
|
|
||||||
|
public SavingsAccountPendingEditData() {}
|
||||||
|
}
|
||||||
+7
-6
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.search.data;
|
package org.apache.fineract.portfolio.search.data;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import org.apache.fineract.portfolio.search.SearchConstants.SearchSupportedResources;
|
import org.apache.fineract.portfolio.search.SearchConstants.SearchSupportedResources;
|
||||||
|
|
||||||
public class SearchConditions {
|
public class SearchConditions {
|
||||||
@@ -37,17 +38,17 @@ public class SearchConditions {
|
|||||||
this.searchResource = searchResource;
|
this.searchResource = searchResource;
|
||||||
this.exactMatch = exactMatch;
|
this.exactMatch = exactMatch;
|
||||||
this.clientSearch = null == searchResource
|
this.clientSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.CLIENTS.name().toLowerCase(Locale.ROOT));
|
||||||
this.groupSearch = null == searchResource
|
this.groupSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.GROUPS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.GROUPS.name().toLowerCase(Locale.ROOT));
|
||||||
this.loanSeach = null == searchResource
|
this.loanSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.LOANS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.LOANS.name().toLowerCase(Locale.ROOT));
|
||||||
this.savingSeach = null == searchResource
|
this.savingSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.SAVINGS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.SAVINGS.name().toLowerCase(Locale.ROOT));
|
||||||
this.shareSeach = null == searchResource
|
this.shareSeach = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.SHARES.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.SHARES.name().toLowerCase(Locale.ROOT));
|
||||||
this.clientIdentifierSearch = null == searchResource
|
this.clientIdentifierSearch = null == searchResource
|
||||||
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase());
|
|| searchResource.toLowerCase(Locale.ROOT).contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
|
public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
|
||||||
|
|||||||
+1
-1
@@ -321,6 +321,6 @@ public class SearchUtil {
|
|||||||
|
|
||||||
public String camelToSnake(final String camelStr) {
|
public String camelToSnake(final String camelStr) {
|
||||||
return camelStr == null ? null
|
return camelStr == null ? null
|
||||||
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
|
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* 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.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.PrePersist;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A versioned, admin-manageable "user agreement" (bank policy/warning) that every user must accept right after
|
||||||
|
* logging in. Only one row may be {@link #active} at a time; enforced transactionally by the write service, not a
|
||||||
|
* DB constraint.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_user_agreement", uniqueConstraints = { @UniqueConstraint(columnNames = "version", name = "uq_user_agreement_version") })
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserAgreement extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "version", nullable = false)
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
@Column(name = "title", length = 200, nullable = false)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Column(name = "body", nullable = false)
|
||||||
|
private String body;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "created_by", nullable = false)
|
||||||
|
private AppUser createdBy;
|
||||||
|
|
||||||
|
@Column(name = "created_on", nullable = false, updatable = false)
|
||||||
|
private OffsetDateTime createdOn;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
private void prePersist() {
|
||||||
|
if (createdOn == null) {
|
||||||
|
createdOn = OffsetDateTime.now(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserAgreement create(final Integer version, final String title, final String body, final AppUser createdBy) {
|
||||||
|
final UserAgreement agreement = new UserAgreement();
|
||||||
|
agreement.version = version;
|
||||||
|
agreement.title = title;
|
||||||
|
agreement.body = body;
|
||||||
|
agreement.createdBy = createdBy;
|
||||||
|
agreement.active = true;
|
||||||
|
return agreement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void activate() {
|
||||||
|
this.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivate() {
|
||||||
|
this.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* 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.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.PrePersist;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit-proof record of a user accepting a given version of the {@link UserAgreement} right after logging in.
|
||||||
|
* {@link #agreementVersion} is a denormalized snapshot of {@code UserAgreement.version} at acceptance time, so the
|
||||||
|
* acceptance record survives future version bumps unaffected.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_user_agreement_acceptance", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = { "appuser_id", "agreement_id" }, name = "uq_user_agreement_acceptance") })
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class UserAgreementAcceptance extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "appuser_id", nullable = false)
|
||||||
|
private AppUser appUser;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
|
@JoinColumn(name = "agreement_id", nullable = false)
|
||||||
|
private UserAgreement agreement;
|
||||||
|
|
||||||
|
@Column(name = "agreement_version", nullable = false)
|
||||||
|
private Integer agreementVersion;
|
||||||
|
|
||||||
|
@Column(name = "accepted_on", nullable = false, updatable = false)
|
||||||
|
private OffsetDateTime acceptedOn;
|
||||||
|
|
||||||
|
@Column(name = "ip_address", length = 45)
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
@Column(name = "user_agent", length = 500)
|
||||||
|
private String userAgent;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
private void prePersist() {
|
||||||
|
if (acceptedOn == null) {
|
||||||
|
acceptedOn = OffsetDateTime.now(ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserAgreementAcceptance create(final AppUser appUser, final UserAgreement agreement, final String ipAddress,
|
||||||
|
final String userAgent) {
|
||||||
|
final UserAgreementAcceptance acceptance = new UserAgreementAcceptance();
|
||||||
|
acceptance.appUser = appUser;
|
||||||
|
acceptance.agreement = agreement;
|
||||||
|
acceptance.agreementVersion = agreement.getVersion();
|
||||||
|
acceptance.ipAddress = ipAddress;
|
||||||
|
acceptance.userAgent = userAgent;
|
||||||
|
return acceptance;
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserAgreementAcceptanceRepository extends JpaRepository<UserAgreementAcceptance, Long> {
|
||||||
|
|
||||||
|
boolean existsByAppUser_IdAndAgreement_Id(Long appUserId, Long agreementId);
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.useradministration.agreement.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface UserAgreementRepository extends JpaRepository<UserAgreement, Long> {
|
||||||
|
|
||||||
|
Optional<UserAgreement> findByActiveTrue();
|
||||||
|
|
||||||
|
List<UserAgreement> findAllByOrderByVersionDesc();
|
||||||
|
}
|
||||||
+1
-1
@@ -39,7 +39,7 @@ public interface BankUserDscRepository extends JpaRepository<BankUserDsc, Long>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Login gate: returns true if the user has at least one usable DSC mapping that's also not
|
* Login gate: returns true if the user has at least one usable DSC mapping that's also not
|
||||||
* past its notAfter date. The {@code dscExpiryDate >= today} clause prevents 2FA-gating a
|
* past its notAfter date. The {@code dscExpiryDate >= today} clause prevents 2FA-gating a
|
||||||
* user who's enrolled but whose cert silently expired between log-ins — they'd otherwise be
|
* user who's enrolled but whose cert silently expired between log-ins — they'd otherwise be
|
||||||
* stuck in a verification loop with a cert the helper would sign with but the server would
|
* stuck in a verification loop with a cert the helper would sign with but the server would
|
||||||
* accept (the verify side filters too, but the gate is what kicks the user into the DSC flow
|
* accept (the verify side filters too, but the gate is what kicks the user into the DSC flow
|
||||||
|
|||||||
+27
@@ -99,4 +99,31 @@ public class CurrentAccountFacilityConfig extends AbstractPersistableCustom<Long
|
|||||||
cfg.currentAccount = account;
|
cfg.currentAccount = account;
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial update: only overwrites fields present (non-null) in the given JSON, leaving all others untouched.
|
||||||
|
* Mirrors {@code SavingsAccountFacilityConfig.updateFrom(JsonObject)}. Used by the Nominee/Digital-Feature
|
||||||
|
* edit-approval flow, which only ever submits the toggles the maker actually touched.
|
||||||
|
*/
|
||||||
|
public void updateFrom(com.google.gson.JsonObject config) {
|
||||||
|
if (config == null) return;
|
||||||
|
if (config.has("atmFacility")) setAtmFacility(config.get("atmFacility").getAsBoolean());
|
||||||
|
if (config.has("atmWithdrawalFacility")) setAtmWithdrawalFacility(config.get("atmWithdrawalFacility").getAsBoolean());
|
||||||
|
if (config.has("upiFacility")) setUpiFacility(config.get("upiFacility").getAsBoolean());
|
||||||
|
if (config.has("posTransactionsFacility")) setPosTransactionsFacility(config.get("posTransactionsFacility").getAsBoolean());
|
||||||
|
if (config.has("chequeBook")) setChequeBook(config.get("chequeBook").getAsBoolean());
|
||||||
|
if (config.has("impsFacility")) setImpsFacility(config.get("impsFacility").getAsBoolean());
|
||||||
|
if (config.has("smsAlerts")) setSmsAlerts(config.get("smsAlerts").getAsBoolean());
|
||||||
|
if (config.has("bbpsFacility")) setBbpsFacility(config.get("bbpsFacility").getAsBoolean());
|
||||||
|
if (config.has("insuranceCover")) setInsuranceCover(config.get("insuranceCover").getAsBoolean());
|
||||||
|
if (config.has("bepgFacility")) setBepgFacility(config.get("bepgFacility").getAsBoolean());
|
||||||
|
if (config.has("neftFacility")) setNeftFacility(config.get("neftFacility").getAsBoolean());
|
||||||
|
if (config.has("rtgsFacility")) setRtgsFacility(config.get("rtgsFacility").getAsBoolean());
|
||||||
|
if (config.has("interBankTotalCapFacility")) setInterBankTotalCapFacility(config.get("interBankTotalCapFacility").getAsBoolean());
|
||||||
|
if (config.has("withdrawalFacility")) setWithdrawalFacility(config.get("withdrawalFacility").getAsBoolean());
|
||||||
|
if (config.has("depositFacilityKyc")) setDepositFacilityKyc(config.get("depositFacilityKyc").getAsBoolean());
|
||||||
|
if (config.has("virtualDebitCard")) setVirtualDebitCard(config.get("virtualDebitCard").getAsBoolean());
|
||||||
|
if (config.has("internetBanking")) setInternetBanking(config.get("internetBanking").getAsBoolean());
|
||||||
|
if (config.has("mobileBanking")) setMobileBanking(config.get("mobileBanking").getAsBoolean());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.domain;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This repository did not previously exist - {@link CurrentAccountFacilityConfig} rows were only ever persisted via
|
||||||
|
* cascade from {@link CurrentAccountRepository} (see {@code CurrentAccount#facilityConfig}, {@code cascade = ALL}).
|
||||||
|
* Added so the Nominee/Digital-Feature edit-approval flow can save the config row directly, mirroring
|
||||||
|
* {@code SavingsAccountFacilityConfigRepository}.
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface CurrentAccountFacilityConfigRepository extends JpaRepository<CurrentAccountFacilityConfig, Long> {
|
||||||
|
|
||||||
|
Optional<CurrentAccountFacilityConfig> findByCurrentAccountId(Long currentAccountId);
|
||||||
|
}
|
||||||
+597
@@ -0,0 +1,597 @@
|
|||||||
|
/**
|
||||||
|
* 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.currentaccount.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.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds a pending edit request for an ACTIVE current account's Nominee + Digital & Account Features.
|
||||||
|
* Presence of a row here with {@code status = PENDING} (enforced via the unique {@link #pendingLockKey}) is itself
|
||||||
|
* the lock - nothing is applied to the real {@code m_current_account_facility_config} / {@code m_client_nominee}
|
||||||
|
* rows until a checker approves (see CurrentAccountEditWritePlatformServiceJpaRepositoryImpl). Mirrors
|
||||||
|
* {@code SavingsAccountPendingEdit} but additionally carries a full point-in-time "wide snapshot" of every business
|
||||||
|
* column on {@link CurrentAccount} at submission time, for audit/UI display purposes only (not used to drive
|
||||||
|
* approve/reject logic, which reads the live account).
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_current_account_pending_edit")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CurrentAccountPendingEdit extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
public static final String STATUS_PENDING = "PENDING";
|
||||||
|
public static final String STATUS_APPROVED = "APPROVED";
|
||||||
|
public static final String STATUS_REJECTED = "REJECTED";
|
||||||
|
|
||||||
|
public static final String NOMINEE_OP_ADD = "ADD";
|
||||||
|
public static final String NOMINEE_OP_UPDATE = "UPDATE";
|
||||||
|
public static final String NOMINEE_OP_DELETE = "DELETE";
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "current_account_id", nullable = false)
|
||||||
|
private CurrentAccount currentAccount;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "createdby_id", nullable = false)
|
||||||
|
private AppUser maker;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "lastmodifiedby_id")
|
||||||
|
private AppUser checker;
|
||||||
|
|
||||||
|
@Column(name = "created_date", nullable = false)
|
||||||
|
private LocalDateTime createdOn;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_date")
|
||||||
|
private LocalDateTime checkedOn;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "reject_reason", length = 500)
|
||||||
|
private String rejectReason;
|
||||||
|
|
||||||
|
/** "CURRENT_ACCOUNT_EDIT:<currentAccountId>" while PENDING, NULL otherwise - the real DB-level lock. */
|
||||||
|
@Column(name = "pending_lock_key", length = 40)
|
||||||
|
private String pendingLockKey;
|
||||||
|
|
||||||
|
/** Links to the audit-only m_portfolio_command_source row created for this edit's submit/approve/reject. */
|
||||||
|
@Column(name = "command_source_id")
|
||||||
|
private Long commandSourceId;
|
||||||
|
|
||||||
|
@Column(name = "hold_transaction_id")
|
||||||
|
private Long holdTransactionId;
|
||||||
|
|
||||||
|
@Column(name = "hold_amount", precision = 19, scale = 6)
|
||||||
|
private BigDecimal holdAmount;
|
||||||
|
|
||||||
|
// ── Digital & Account Features (same names as m_current_account_facility_config) ──
|
||||||
|
@Column(name = "facility_config_submitted", nullable = false)
|
||||||
|
private boolean facilityConfigSubmitted = false;
|
||||||
|
|
||||||
|
@Column(name = "atm_facility")
|
||||||
|
private Boolean atmFacility;
|
||||||
|
|
||||||
|
@Column(name = "atm_withdrawal_facility")
|
||||||
|
private Boolean atmWithdrawalFacility;
|
||||||
|
|
||||||
|
@Column(name = "upi_facility")
|
||||||
|
private Boolean upiFacility;
|
||||||
|
|
||||||
|
@Column(name = "pos_transactions_facility")
|
||||||
|
private Boolean posTransactionsFacility;
|
||||||
|
|
||||||
|
@Column(name = "cheque_book")
|
||||||
|
private Boolean chequeBook;
|
||||||
|
|
||||||
|
@Column(name = "imps_facility")
|
||||||
|
private Boolean impsFacility;
|
||||||
|
|
||||||
|
@Column(name = "sms_alerts")
|
||||||
|
private Boolean smsAlerts;
|
||||||
|
|
||||||
|
@Column(name = "bbps_facility")
|
||||||
|
private Boolean bbpsFacility;
|
||||||
|
|
||||||
|
@Column(name = "insurance_cover")
|
||||||
|
private Boolean insuranceCover;
|
||||||
|
|
||||||
|
@Column(name = "bepg_facility")
|
||||||
|
private Boolean bepgFacility;
|
||||||
|
|
||||||
|
@Column(name = "neft_facility")
|
||||||
|
private Boolean neftFacility;
|
||||||
|
|
||||||
|
@Column(name = "rtgs_facility")
|
||||||
|
private Boolean rtgsFacility;
|
||||||
|
|
||||||
|
@Column(name = "inter_bank_total_cap_facility")
|
||||||
|
private Boolean interBankTotalCapFacility;
|
||||||
|
|
||||||
|
@Column(name = "withdrawal_facility")
|
||||||
|
private Boolean withdrawalFacility;
|
||||||
|
|
||||||
|
@Column(name = "deposit_facility_kyc")
|
||||||
|
private Boolean depositFacilityKyc;
|
||||||
|
|
||||||
|
@Column(name = "virtual_debit_card")
|
||||||
|
private Boolean virtualDebitCard;
|
||||||
|
|
||||||
|
@Column(name = "internet_banking")
|
||||||
|
private Boolean internetBanking;
|
||||||
|
|
||||||
|
@Column(name = "mobile_banking")
|
||||||
|
private Boolean mobileBanking;
|
||||||
|
|
||||||
|
// ── Nominee change - one per request (add / update / delete) ──
|
||||||
|
@Column(name = "nominee_submitted", nullable = false)
|
||||||
|
private boolean nomineeSubmitted = false;
|
||||||
|
|
||||||
|
@Column(name = "nominee_operation", length = 10)
|
||||||
|
private String nomineeOperation;
|
||||||
|
|
||||||
|
@Column(name = "nominee_id")
|
||||||
|
private Long nomineeId;
|
||||||
|
|
||||||
|
@Column(name = "nominee_client_id")
|
||||||
|
private Long nomineeClientId;
|
||||||
|
|
||||||
|
@Column(name = "relationship_cv_id")
|
||||||
|
private Long relationshipCvId;
|
||||||
|
|
||||||
|
@Column(name = "percentage_share", precision = 5, scale = 2)
|
||||||
|
private BigDecimal percentageShare;
|
||||||
|
|
||||||
|
@Column(name = "nominee_is_active")
|
||||||
|
private Boolean nomineeIsActive;
|
||||||
|
|
||||||
|
@Column(name = "nominee_is_minor")
|
||||||
|
private Boolean nomineeIsMinor;
|
||||||
|
|
||||||
|
@Column(name = "guardian_client_id")
|
||||||
|
private Long guardianClientId;
|
||||||
|
|
||||||
|
@Column(name = "guardian_relationship_cv_id")
|
||||||
|
private Long guardianRelationshipCvId;
|
||||||
|
|
||||||
|
@Column(name = "effective_date")
|
||||||
|
private LocalDate effectiveDate;
|
||||||
|
|
||||||
|
@Column(name = "nominee_remarks", length = 500)
|
||||||
|
private String nomineeRemarks;
|
||||||
|
|
||||||
|
// ── Wide point-in-time snapshot of every business column on CurrentAccount at submission time ──
|
||||||
|
// (audit/UI display only - approve()/reject() logic never reads these, it reads the live account)
|
||||||
|
@Column(name = "account_no", length = 20)
|
||||||
|
private String snapshotAccountNo;
|
||||||
|
|
||||||
|
@Column(name = "external_id", length = 100)
|
||||||
|
private String snapshotExternalId;
|
||||||
|
|
||||||
|
@Column(name = "client_id")
|
||||||
|
private Long snapshotClientId;
|
||||||
|
|
||||||
|
@Column(name = "product_id")
|
||||||
|
private Long snapshotProductId;
|
||||||
|
|
||||||
|
@Column(name = "office_id")
|
||||||
|
private Long snapshotOfficeId;
|
||||||
|
|
||||||
|
@Column(name = "staff_id")
|
||||||
|
private Long snapshotStaffId;
|
||||||
|
|
||||||
|
@Column(name = "status_enum")
|
||||||
|
private Integer snapshotStatusEnum;
|
||||||
|
|
||||||
|
@Column(name = "sub_status_enum")
|
||||||
|
private Integer snapshotSubStatusEnum;
|
||||||
|
|
||||||
|
@Column(name = "account_type_enum")
|
||||||
|
private Integer snapshotAccountTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "submittedon_date")
|
||||||
|
private LocalDate snapshotSubmittedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "approvedon_date")
|
||||||
|
private LocalDate snapshotApprovedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "activatedon_date")
|
||||||
|
private LocalDate snapshotActivatedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "rejectedon_date")
|
||||||
|
private LocalDate snapshotRejectedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "withdrawnon_date")
|
||||||
|
private LocalDate snapshotWithdrawnOnDate;
|
||||||
|
|
||||||
|
@Column(name = "closedon_date")
|
||||||
|
private LocalDate snapshotClosedOnDate;
|
||||||
|
|
||||||
|
@Column(name = "submittedon_userid")
|
||||||
|
private Long snapshotSubmittedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "approvedon_userid")
|
||||||
|
private Long snapshotApprovedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "activatedon_userid")
|
||||||
|
private Long snapshotActivatedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "rejectedon_userid")
|
||||||
|
private Long snapshotRejectedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "withdrawnon_userid")
|
||||||
|
private Long snapshotWithdrawnOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "closedon_userid")
|
||||||
|
private Long snapshotClosedOnUserId;
|
||||||
|
|
||||||
|
@Column(name = "currency_code", length = 3)
|
||||||
|
private String snapshotCurrencyCode;
|
||||||
|
|
||||||
|
@Column(name = "currency_digits")
|
||||||
|
private Integer snapshotCurrencyDigits;
|
||||||
|
|
||||||
|
@Column(name = "currency_multiplesof")
|
||||||
|
private Integer snapshotCurrencyMultiplesOf;
|
||||||
|
|
||||||
|
@Column(name = "nominal_annual_interest_rate", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotNominalAnnualInterestRate;
|
||||||
|
|
||||||
|
@Column(name = "interest_compounding_period_enum")
|
||||||
|
private Integer snapshotInterestCompoundingPeriodEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_posting_period_enum")
|
||||||
|
private Integer snapshotInterestPostingPeriodEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculation_type_enum")
|
||||||
|
private Integer snapshotInterestCalculationTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculation_days_in_year_type_enum")
|
||||||
|
private Integer snapshotInterestCalculationDaysInYearTypeEnum;
|
||||||
|
|
||||||
|
@Column(name = "min_balance_for_interest_calculation", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinBalanceForInterestCalculation;
|
||||||
|
|
||||||
|
@Column(name = "start_interest_calculation_date")
|
||||||
|
private LocalDate snapshotStartInterestCalculationDate;
|
||||||
|
|
||||||
|
@Column(name = "accrued_till_date")
|
||||||
|
private LocalDate snapshotAccruedTillDate;
|
||||||
|
|
||||||
|
@Column(name = "last_interest_calculation_date")
|
||||||
|
private LocalDate snapshotLastInterestCalculationDate;
|
||||||
|
|
||||||
|
@Column(name = "last_closed_business_date")
|
||||||
|
private LocalDate snapshotLastClosedBusinessDate;
|
||||||
|
|
||||||
|
@Column(name = "withhold_tax")
|
||||||
|
private Boolean snapshotWithholdTax;
|
||||||
|
|
||||||
|
@Column(name = "min_required_opening_balance", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinRequiredOpeningBalance;
|
||||||
|
|
||||||
|
@Column(name = "lockin_period_frequency")
|
||||||
|
private Integer snapshotLockinPeriodFrequency;
|
||||||
|
|
||||||
|
@Column(name = "lockin_period_frequency_enum")
|
||||||
|
private Integer snapshotLockinPeriodFrequencyEnum;
|
||||||
|
|
||||||
|
@Column(name = "lockedin_until_date_derived")
|
||||||
|
private LocalDate snapshotLockedInUntilDate;
|
||||||
|
|
||||||
|
@Column(name = "enforce_min_required_balance")
|
||||||
|
private Boolean snapshotEnforceMinRequiredBalance;
|
||||||
|
|
||||||
|
@Column(name = "min_required_balance", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinRequiredBalance;
|
||||||
|
|
||||||
|
@Column(name = "allow_overdraft")
|
||||||
|
private Boolean snapshotAllowOverdraft;
|
||||||
|
|
||||||
|
@Column(name = "overdraft_limit", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotOverdraftLimit;
|
||||||
|
|
||||||
|
@Column(name = "nominal_annual_interest_rate_overdraft", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotNominalAnnualInterestRateOverdraft;
|
||||||
|
|
||||||
|
@Column(name = "min_overdraft_for_interest_calculation", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMinOverdraftForInterestCalculation;
|
||||||
|
|
||||||
|
@Column(name = "is_lien_allowed")
|
||||||
|
private Boolean snapshotLienAllowed;
|
||||||
|
|
||||||
|
@Column(name = "max_allowed_lien_limit", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotMaxAllowedLienLimit;
|
||||||
|
|
||||||
|
@Column(name = "on_hold_funds_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotOnHoldFundsDerived;
|
||||||
|
|
||||||
|
@Column(name = "account_balance_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotAccountBalanceDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_deposits_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalDepositsDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_withdrawals_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalWithdrawalsDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_interest_earned_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalInterestEarnedDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_interest_posted_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalInterestPostedDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_overdraft_interest_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalOverdraftInterestDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_withhold_tax_derived", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalWithholdTaxDerived;
|
||||||
|
|
||||||
|
@Column(name = "total_savings_amount_on_hold", precision = 19, scale = 6)
|
||||||
|
private BigDecimal snapshotTotalSavingsAmountOnHold;
|
||||||
|
|
||||||
|
@Column(name = "withdrawal_fee_for_transfer")
|
||||||
|
private Boolean snapshotWithdrawalFeeForTransfer;
|
||||||
|
|
||||||
|
@Column(name = "reason_for_block", length = 500)
|
||||||
|
private String snapshotReasonForBlock;
|
||||||
|
|
||||||
|
@Column(name = "msme")
|
||||||
|
private Boolean snapshotMsme;
|
||||||
|
|
||||||
|
@Column(name = "auto_debit_facility")
|
||||||
|
private Boolean snapshotAutoDebitFacility;
|
||||||
|
|
||||||
|
@Column(name = "balance_swap_option")
|
||||||
|
private Boolean snapshotBalanceSwapOption;
|
||||||
|
|
||||||
|
@Column(name = "establishment_date")
|
||||||
|
private LocalDate snapshotEstablishmentDate;
|
||||||
|
|
||||||
|
@Column(name = "start_operation_date")
|
||||||
|
private LocalDate snapshotStartOperationDate;
|
||||||
|
|
||||||
|
@Column(name = "is_non_individual_client")
|
||||||
|
private Boolean snapshotNonIndividualClient;
|
||||||
|
|
||||||
|
@Column(name = "signatory_rights", length = 100)
|
||||||
|
private String snapshotSignatoryRights;
|
||||||
|
|
||||||
|
@Column(name = "cbs_account_type", length = 100)
|
||||||
|
private String snapshotCbsAccountType;
|
||||||
|
|
||||||
|
@Column(name = "customer_segment", length = 100)
|
||||||
|
private String snapshotCustomerSegment;
|
||||||
|
|
||||||
|
@Column(name = "account_status", length = 50)
|
||||||
|
private String snapshotAccountStatus;
|
||||||
|
|
||||||
|
@Column(name = "mode_of_operation", length = 50)
|
||||||
|
private String snapshotModeOfOperation;
|
||||||
|
|
||||||
|
@Column(name = "special_category", length = 100)
|
||||||
|
private String snapshotSpecialCategory;
|
||||||
|
|
||||||
|
@Column(name = "introducer_client_id")
|
||||||
|
private Long snapshotIntroducerClientId;
|
||||||
|
|
||||||
|
@Column(name = "introducer_name", length = 200)
|
||||||
|
private String snapshotIntroducerName;
|
||||||
|
|
||||||
|
@Column(name = "introducer_cif_number", length = 50)
|
||||||
|
private String snapshotIntroducerCifNumber;
|
||||||
|
|
||||||
|
@Column(name = "nominee_data", columnDefinition = "TEXT")
|
||||||
|
private String snapshotNomineeData;
|
||||||
|
|
||||||
|
@Column(name = "interest_calculate_selector")
|
||||||
|
private Integer snapshotInterestCalculateSelector;
|
||||||
|
|
||||||
|
@Column(name = "average_period", length = 20)
|
||||||
|
private String snapshotAveragePeriod;
|
||||||
|
|
||||||
|
@Column(name = "auto_post")
|
||||||
|
private Boolean snapshotAutoPost;
|
||||||
|
|
||||||
|
@Column(name = "frequency_fix_day")
|
||||||
|
private Integer snapshotFrequencyFixDay;
|
||||||
|
|
||||||
|
@Column(name = "enable_lockin_period")
|
||||||
|
private Boolean snapshotEnableLockinPeriod;
|
||||||
|
|
||||||
|
@Column(name = "balance_frequency", length = 50)
|
||||||
|
private String snapshotBalanceFrequency;
|
||||||
|
|
||||||
|
@Column(name = "avg_balance_calc_period", length = 10)
|
||||||
|
private String snapshotAvgBalanceCalcPeriod;
|
||||||
|
|
||||||
|
@Column(name = "account_closed_zero_balance_in_day_closing")
|
||||||
|
private Boolean snapshotAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|
||||||
|
@Column(name = "valid_for_period", length = 10)
|
||||||
|
private String snapshotValidForPeriod;
|
||||||
|
|
||||||
|
@Column(name = "od_account_closed_zero_balance_in_day_closing")
|
||||||
|
private Boolean snapshotOdAccountClosedZeroBalanceInDayClosing;
|
||||||
|
|
||||||
|
@Column(name = "is_linking_enable")
|
||||||
|
private Boolean snapshotShareLinkingEnabled;
|
||||||
|
|
||||||
|
@Column(name = "terminate_true")
|
||||||
|
private Boolean snapshotTerminateTrue;
|
||||||
|
|
||||||
|
protected CurrentAccountPendingEdit() {}
|
||||||
|
|
||||||
|
public CurrentAccountPendingEdit(final CurrentAccount currentAccount, final AppUser maker) {
|
||||||
|
this.currentAccount = currentAccount;
|
||||||
|
this.maker = maker;
|
||||||
|
this.createdOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.status = STATUS_PENDING;
|
||||||
|
this.pendingLockKey = lockKeyFor(currentAccount.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String lockKeyFor(final Long currentAccountId) {
|
||||||
|
return "CURRENT_ACCOUNT_EDIT:" + currentAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a new pending-edit row and copies every business column off the live {@code account} onto this
|
||||||
|
* request's wide snapshot fields, for audit/UI display purposes only.
|
||||||
|
*/
|
||||||
|
public static CurrentAccountPendingEdit snapshotOf(final CurrentAccount account, final AppUser maker) {
|
||||||
|
final CurrentAccountPendingEdit p = new CurrentAccountPendingEdit(account, maker);
|
||||||
|
|
||||||
|
p.snapshotAccountNo = account.getAccountNo();
|
||||||
|
p.snapshotExternalId = account.getExternalId();
|
||||||
|
p.snapshotClientId = account.getClient() != null ? account.getClient().getId() : null;
|
||||||
|
p.snapshotProductId = account.getProductId();
|
||||||
|
p.snapshotOfficeId = account.getOffice() != null ? account.getOffice().getId() : null;
|
||||||
|
p.snapshotStaffId = account.getStaff() != null ? account.getStaff().getId() : null;
|
||||||
|
p.snapshotStatusEnum = account.getStatusEnum();
|
||||||
|
p.snapshotSubStatusEnum = account.getSubStatusEnum();
|
||||||
|
p.snapshotAccountTypeEnum = account.getAccountTypeEnum();
|
||||||
|
|
||||||
|
p.snapshotSubmittedOnDate = account.getSubmittedOnDate();
|
||||||
|
p.snapshotApprovedOnDate = account.getApprovedOnDate();
|
||||||
|
p.snapshotActivatedOnDate = account.getActivatedOnDate();
|
||||||
|
p.snapshotRejectedOnDate = account.getRejectedOnDate();
|
||||||
|
p.snapshotWithdrawnOnDate = account.getWithdrawnOnDate();
|
||||||
|
p.snapshotClosedOnDate = account.getClosedOnDate();
|
||||||
|
|
||||||
|
p.snapshotSubmittedOnUserId = account.getSubmittedOnByUser() != null ? account.getSubmittedOnByUser().getId() : null;
|
||||||
|
p.snapshotApprovedOnUserId = account.getApprovedOnByUser() != null ? account.getApprovedOnByUser().getId() : null;
|
||||||
|
p.snapshotActivatedOnUserId = account.getActivatedOnByUser() != null ? account.getActivatedOnByUser().getId() : null;
|
||||||
|
p.snapshotRejectedOnUserId = account.getRejectedOnByUser() != null ? account.getRejectedOnByUser().getId() : null;
|
||||||
|
p.snapshotWithdrawnOnUserId = account.getWithdrawnOnByUser() != null ? account.getWithdrawnOnByUser().getId() : null;
|
||||||
|
p.snapshotClosedOnUserId = account.getClosedOnByUser() != null ? account.getClosedOnByUser().getId() : null;
|
||||||
|
|
||||||
|
p.snapshotCurrencyCode = account.getCurrencyCode();
|
||||||
|
p.snapshotCurrencyDigits = account.getCurrencyDigits();
|
||||||
|
p.snapshotCurrencyMultiplesOf = account.getCurrencyMultiplesOf();
|
||||||
|
|
||||||
|
p.snapshotNominalAnnualInterestRate = account.getNominalAnnualInterestRate();
|
||||||
|
p.snapshotInterestCompoundingPeriodEnum = account.getInterestCompoundingPeriodEnum();
|
||||||
|
p.snapshotInterestPostingPeriodEnum = account.getInterestPostingPeriodEnum();
|
||||||
|
p.snapshotInterestCalculationTypeEnum = account.getInterestCalculationTypeEnum();
|
||||||
|
p.snapshotInterestCalculationDaysInYearTypeEnum = account.getInterestCalculationDaysInYearTypeEnum();
|
||||||
|
p.snapshotMinBalanceForInterestCalculation = account.getMinBalanceForInterestCalculation();
|
||||||
|
p.snapshotStartInterestCalculationDate = account.getStartInterestCalculationDate();
|
||||||
|
p.snapshotAccruedTillDate = account.getAccruedTillDate();
|
||||||
|
p.snapshotLastInterestCalculationDate = account.getLastInterestCalculationDate();
|
||||||
|
p.snapshotLastClosedBusinessDate = account.getLastClosedBusinessDate();
|
||||||
|
|
||||||
|
p.snapshotWithholdTax = account.isWithholdTax();
|
||||||
|
|
||||||
|
p.snapshotMinRequiredOpeningBalance = account.getMinRequiredOpeningBalance();
|
||||||
|
p.snapshotLockinPeriodFrequency = account.getLockinPeriodFrequency();
|
||||||
|
p.snapshotLockinPeriodFrequencyEnum = account.getLockinPeriodFrequencyEnum();
|
||||||
|
p.snapshotLockedInUntilDate = account.getLockedInUntilDate();
|
||||||
|
p.snapshotEnforceMinRequiredBalance = account.isEnforceMinRequiredBalance();
|
||||||
|
p.snapshotMinRequiredBalance = account.getMinRequiredBalance();
|
||||||
|
|
||||||
|
p.snapshotAllowOverdraft = account.isAllowOverdraft();
|
||||||
|
p.snapshotOverdraftLimit = account.getOverdraftLimit();
|
||||||
|
p.snapshotNominalAnnualInterestRateOverdraft = account.getNominalAnnualInterestRateOverdraft();
|
||||||
|
p.snapshotMinOverdraftForInterestCalculation = account.getMinOverdraftForInterestCalculation();
|
||||||
|
|
||||||
|
p.snapshotLienAllowed = account.isLienAllowed();
|
||||||
|
p.snapshotMaxAllowedLienLimit = account.getMaxAllowedLienLimit();
|
||||||
|
p.snapshotOnHoldFundsDerived = account.getOnHoldFundsDerived();
|
||||||
|
|
||||||
|
p.snapshotAccountBalanceDerived = account.getAccountBalanceDerived();
|
||||||
|
p.snapshotTotalDepositsDerived = account.getTotalDepositsDerived();
|
||||||
|
p.snapshotTotalWithdrawalsDerived = account.getTotalWithdrawalsDerived();
|
||||||
|
p.snapshotTotalInterestEarnedDerived = account.getTotalInterestEarnedDerived();
|
||||||
|
p.snapshotTotalInterestPostedDerived = account.getTotalInterestPostedDerived();
|
||||||
|
p.snapshotTotalOverdraftInterestDerived = account.getTotalOverdraftInterestDerived();
|
||||||
|
p.snapshotTotalWithholdTaxDerived = account.getTotalWithholdTaxDerived();
|
||||||
|
p.snapshotTotalSavingsAmountOnHold = account.getTotalSavingsAmountOnHold();
|
||||||
|
|
||||||
|
p.snapshotWithdrawalFeeForTransfer = account.isWithdrawalFeeForTransfer();
|
||||||
|
p.snapshotReasonForBlock = account.getReasonForBlock();
|
||||||
|
|
||||||
|
p.snapshotMsme = account.isMsme();
|
||||||
|
p.snapshotAutoDebitFacility = account.isAutoDebitFacility();
|
||||||
|
p.snapshotBalanceSwapOption = account.isBalanceSwapOption();
|
||||||
|
p.snapshotEstablishmentDate = account.getEstablishmentDate();
|
||||||
|
p.snapshotStartOperationDate = account.getStartOperationDate();
|
||||||
|
p.snapshotNonIndividualClient = account.isNonIndividualClient();
|
||||||
|
p.snapshotSignatoryRights = account.getSignatoryRights();
|
||||||
|
|
||||||
|
p.snapshotCbsAccountType = account.getCbsAccountType();
|
||||||
|
p.snapshotCustomerSegment = account.getCustomerSegment();
|
||||||
|
p.snapshotAccountStatus = account.getAccountStatus();
|
||||||
|
p.snapshotModeOfOperation = account.getModeOfOperation();
|
||||||
|
p.snapshotSpecialCategory = account.getSpecialCategory();
|
||||||
|
|
||||||
|
p.snapshotIntroducerClientId = account.getIntroducerClientId();
|
||||||
|
p.snapshotIntroducerName = account.getIntroducerName();
|
||||||
|
p.snapshotIntroducerCifNumber = account.getIntroducerCifNumber();
|
||||||
|
|
||||||
|
p.snapshotNomineeData = account.getNomineeData();
|
||||||
|
|
||||||
|
p.snapshotInterestCalculateSelector = account.getInterestCalculateSelector();
|
||||||
|
p.snapshotAveragePeriod = account.getAveragePeriod();
|
||||||
|
p.snapshotAutoPost = account.isAutoPost();
|
||||||
|
p.snapshotFrequencyFixDay = account.getFrequencyFixDay();
|
||||||
|
p.snapshotEnableLockinPeriod = account.isEnableLockinPeriod();
|
||||||
|
p.snapshotBalanceFrequency = account.getBalanceFrequency();
|
||||||
|
p.snapshotAvgBalanceCalcPeriod = account.getAvgBalanceCalcPeriod();
|
||||||
|
|
||||||
|
p.snapshotAccountClosedZeroBalanceInDayClosing = account.getAccountClosedZeroBalanceInDayClosing();
|
||||||
|
p.snapshotValidForPeriod = account.getValidForPeriod();
|
||||||
|
p.snapshotOdAccountClosedZeroBalanceInDayClosing = account.isOdAccountClosedZeroBalanceInDayClosing();
|
||||||
|
|
||||||
|
p.snapshotShareLinkingEnabled = account.isShareLinkingEnabled();
|
||||||
|
p.snapshotTerminateTrue = account.isTerminateTrue();
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPending() {
|
||||||
|
return STATUS_PENDING.equals(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markApproved(final AppUser checker) {
|
||||||
|
this.status = STATUS_APPROVED;
|
||||||
|
this.checker = checker;
|
||||||
|
this.checkedOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.pendingLockKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markRejected(final AppUser checker, final String reason) {
|
||||||
|
this.status = STATUS_REJECTED;
|
||||||
|
this.checker = checker;
|
||||||
|
this.checkedOn = DateUtils.getLocalDateTimeOfTenant();
|
||||||
|
this.rejectReason = reason;
|
||||||
|
this.pendingLockKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -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.currentaccount.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CurrentAccountPendingEditRepository extends JpaRepository<CurrentAccountPendingEdit, Long> {
|
||||||
|
|
||||||
|
Optional<CurrentAccountPendingEdit> findByCurrentAccountIdAndStatus(Long currentAccountId, String status);
|
||||||
|
|
||||||
|
List<CurrentAccountPendingEdit> findByStatus(String status);
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.exception;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown at edit-submission time when the account's available balance cannot cover the charge for
|
||||||
|
* the digital/account feature(s) the maker is requesting to enable.
|
||||||
|
*/
|
||||||
|
public class CurrentAccountEditInsufficientBalanceException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditInsufficientBalanceException(final Long currentAccountId, final BigDecimal availableBalance,
|
||||||
|
final BigDecimal requiredAmount) {
|
||||||
|
super("error.msg.currentaccount.edit.insufficient.balance",
|
||||||
|
"Account balance is insufficient for the requested feature(s). Available balance: " + availableBalance
|
||||||
|
+ ", required: " + requiredAmount,
|
||||||
|
currentAccountId, availableBalance, requiredAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountEditPendingApprovalException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditPendingApprovalException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.edit.pending.approval.exists",
|
||||||
|
"Current account with identifier " + currentAccountId + " already has a Nominee/Digital-Feature edit pending approval",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -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.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountEditSelfApprovalNotAllowedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountEditSelfApprovalNotAllowedException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.edit.self.approval.not.allowed",
|
||||||
|
"The maker of the pending Nominee/Digital-Feature edit for current account " + currentAccountId
|
||||||
|
+ " cannot also approve or reject it",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class CurrentAccountNoPendingEditException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CurrentAccountNoPendingEditException(final Long currentAccountId) {
|
||||||
|
super("error.msg.currentaccount.no.pending.edit",
|
||||||
|
"Current account with identifier " + currentAccountId + " has no Nominee/Digital-Feature edit awaiting approval",
|
||||||
|
currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.currentaccount.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.portfolio.currentaccount.data.CurrentAccountPendingEditData;
|
||||||
|
|
||||||
|
public interface CurrentAccountEditWritePlatformService {
|
||||||
|
|
||||||
|
CommandProcessingResult submitEditForApproval(Long currentAccountId, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult approveEdit(Long currentAccountId);
|
||||||
|
|
||||||
|
CommandProcessingResult rejectEdit(Long currentAccountId, String rejectReason);
|
||||||
|
|
||||||
|
CurrentAccountPendingEditData retrievePendingEdit(Long currentAccountId);
|
||||||
|
|
||||||
|
Collection<CurrentAccountPendingEditData> retrieveAllPendingEdits();
|
||||||
|
}
|
||||||
+3
-1
@@ -51,7 +51,9 @@ public class CurrentAccountNumberGenerator {
|
|||||||
if (val > maxVal) {
|
if (val > maxVal) {
|
||||||
maxVal = val;
|
maxVal = val;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException ignored) {}
|
} catch (NumberFormatException ignored) {
|
||||||
|
// non-numeric suffix - skip when computing the max existing sequence
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.currentaccount.service;
|
package org.apache.fineract.portfolio.currentaccount.service;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
|
||||||
@@ -42,4 +43,13 @@ public interface CurrentAccountWritePlatformService {
|
|||||||
CommandProcessingResult deleteApplication(Long accountId);
|
CommandProcessingResult deleteApplication(Long accountId);
|
||||||
|
|
||||||
CommandProcessingResult terminateAccount(Long accountId, JsonCommand command);
|
CommandProcessingResult terminateAccount(Long accountId, JsonCommand command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves each of the given (newly-enabled) facility types to its mapped charge via
|
||||||
|
* {@code m_facility_charge_mapping}, creates a {@code CurrentAccountCharge} for it (skipping any facility type
|
||||||
|
* for which the account already has a paid, non-waived charge for the same mapped {@code Charge}), collects it
|
||||||
|
* from the account's running balance as of today, and posts the corresponding GL journal entries. Reuses the
|
||||||
|
* same private charge-creation-and-collection helper that {@code activate()} uses for activation-time charges.
|
||||||
|
*/
|
||||||
|
void applyAndCollectFacilityCharges(Long currentAccountId, Set<String> newlyEnabledFacilityTypes);
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -54,7 +54,6 @@ import com.google.gson.JsonElement;
|
|||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.MonthDay;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -76,7 +75,6 @@ import org.apache.fineract.portfolio.savings.DepositAccountType;
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
|
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
|
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
|
||||||
|
|||||||
-7
@@ -56,18 +56,11 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountT
|
|||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.glAccountIdParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
|
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
|
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.OneToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_deposit_limit_kyc")
|
@Table(name = "m_current_product_deposit_limit_kyc")
|
||||||
@@ -57,12 +58,12 @@ public class CurrentProductDepositLimitKyc extends AbstractPersistableCustom<Lon
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductDepositLimitKyc() {
|
protected CurrentProductDepositLimitKyc() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
|
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
|
||||||
this();
|
this();
|
||||||
this.currentProduct = currentProduct;
|
this.currentProduct = currentProduct;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.OneToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_facility_config")
|
@Table(name = "m_current_product_facility_config")
|
||||||
@@ -87,12 +88,12 @@ public class CurrentProductFacilityConfig extends AbstractPersistableCustom<Long
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductFacilityConfig() {
|
protected CurrentProductFacilityConfig() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
|
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
|
||||||
this();
|
this();
|
||||||
this.currentProduct = currentProduct;
|
this.currentProduct = currentProduct;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
public CurrentProduct getCurrentProduct() { return currentProduct; }
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import jakarta.persistence.ManyToOne;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_current_product_facility_limits")
|
@Table(name = "m_current_product_facility_limits")
|
||||||
@@ -51,7 +52,6 @@ public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long>
|
|||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
protected CurrentProductFacilityLimit() {
|
protected CurrentProductFacilityLimit() {
|
||||||
this.createdDate = LocalDateTime.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
|
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
|
||||||
@@ -62,6 +62,7 @@ public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long>
|
|||||||
this.generalLimit = generalLimit;
|
this.generalLimit = generalLimit;
|
||||||
this.minorAccountLimit = minorAccountLimit;
|
this.minorAccountLimit = minorAccountLimit;
|
||||||
this.smallSavingLimit = smallSavingLimit;
|
this.smallSavingLimit = smallSavingLimit;
|
||||||
|
this.createdDate = DateUtils.getLocalDateTimeOfTenant();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
|
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
|
||||||
|
|||||||
-2
@@ -19,13 +19,11 @@
|
|||||||
package org.apache.fineract.portfolio.currentproduct.service;
|
package org.apache.fineract.portfolio.currentproduct.service;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
+6
-5
@@ -95,23 +95,24 @@ public class FileContentStoreService implements ContentStoreService {
|
|||||||
|
|
||||||
final var target = Files.exists(getPath(safePath, false)) ? getPath(safePath, false) : getPath(safePath, true);
|
final var target = Files.exists(getPath(safePath, false)) ? getPath(safePath, false) : getPath(safePath, true);
|
||||||
|
|
||||||
try (var in = is) {
|
try (var in = is; var out = new FileOutputStream(target.toString())) {
|
||||||
IOUtils.copy(in, new FileOutputStream(target.toString()));
|
IOUtils.copy(in, out);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ContentStoreException(e);
|
throw new ContentStoreException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var relativePath = getRelativePath(target);
|
final var relativePath = getRelativePath(target);
|
||||||
|
|
||||||
try {
|
try (var in = getInputStream(target)) {
|
||||||
postUploadContentPolicy
|
postUploadContentPolicy.check(ContentPolicyContext.builder().path(path).inputStream(in).mimeType(mimeType).build());
|
||||||
.check(ContentPolicyContext.builder().path(path).inputStream(getInputStream(target)).mimeType(mimeType).build());
|
|
||||||
} catch (ContentPolicyException e) {
|
} catch (ContentPolicyException e) {
|
||||||
log.warn("Delete file because it didn't comply with the post-upload policy check: {} - {}", target, e.getMessage());
|
log.warn("Delete file because it didn't comply with the post-upload policy check: {} - {}", target, e.getMessage());
|
||||||
|
|
||||||
delete(relativePath.toString());
|
delete(relativePath.toString());
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ContentStoreException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativePath.toString();
|
return relativePath.toString();
|
||||||
|
|||||||
+24
-4
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.documentmanagement.service;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -31,6 +32,7 @@ import org.apache.commons.lang3.Strings;
|
|||||||
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
import org.apache.fineract.infrastructure.contentstore.service.ContentStoreService;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntitySignatureIdAdapter;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateRequest;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateRequest;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
|
||||||
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentDeleteRequest;
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentDeleteRequest;
|
||||||
@@ -59,6 +61,7 @@ public class DocumentWritePlatformServiceImpl implements DocumentWritePlatformSe
|
|||||||
private final ContentStoreService storeService;
|
private final ContentStoreService storeService;
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
private final FineractProperties properties;
|
private final FineractProperties properties;
|
||||||
|
private final List<EntitySignatureIdAdapter> signatureIdAdapters;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -74,13 +77,30 @@ public class DocumentWritePlatformServiceImpl implements DocumentWritePlatformSe
|
|||||||
|
|
||||||
path = storeService.upload(path, request.getStream(), request.getType());
|
path = storeService.upload(path, request.getStream(), request.getType());
|
||||||
|
|
||||||
final var doc = new Document().setParentEntityType(request.getEntityType()).setParentEntityId(request.getEntityId())
|
final var docName = Optional.ofNullable(request.getName()).orElse(request.getFileName());
|
||||||
.setName(Optional.ofNullable(request.getName()).orElse(request.getFileName())).setFileName(request.getFileName())
|
|
||||||
.setSize(request.getSize()).setType(request.getType()).setDescription(request.getDescription()).setLocation(path)
|
// Some document "slots" (e.g. a client's signature) are single-slot, mirroring
|
||||||
.setStorageType(storeService.getType().getValue());
|
// m_client.image_id: re-upload should replace the existing row in place rather than
|
||||||
|
// leave the old one behind as an orphan whose ordering readers then disagree about.
|
||||||
|
// Adapters recognize their own (entityType, name) convention and own the pointer.
|
||||||
|
final var signatureAdapter = signatureIdAdapters.stream()
|
||||||
|
.filter(adapter -> adapter.accept(request.getEntityType(), docName)).findFirst();
|
||||||
|
|
||||||
|
final var doc = signatureAdapter.flatMap(adapter -> adapter.get(request.getEntityId()))
|
||||||
|
.flatMap(result -> documentRepository.findById(result.getId())).map(existing -> {
|
||||||
|
// replacing an existing single-slot document: drop the old file, reuse the row
|
||||||
|
storeService.delete(existing.getLocation());
|
||||||
|
return existing;
|
||||||
|
}).orElseGet(Document::new);
|
||||||
|
|
||||||
|
doc.setParentEntityType(request.getEntityType()).setParentEntityId(request.getEntityId()).setName(docName)
|
||||||
|
.setFileName(request.getFileName()).setSize(request.getSize()).setType(request.getType())
|
||||||
|
.setDescription(request.getDescription()).setLocation(path).setStorageType(storeService.getType().getValue());
|
||||||
|
|
||||||
documentRepository.save(doc);
|
documentRepository.save(doc);
|
||||||
|
|
||||||
|
signatureAdapter.ifPresent(adapter -> adapter.set(request.getEntityId(), doc.getId()));
|
||||||
|
|
||||||
businessEventNotifierService.notifyPostBusinessEvent(new DocumentCreatedBusinessEvent(documentMapper.map(doc)));
|
businessEventNotifierService.notifyPostBusinessEvent(new DocumentCreatedBusinessEvent(documentMapper.map(doc)));
|
||||||
|
|
||||||
return DocumentCreateResponse.builder().resourceId(doc.getId()).resourceIdentifier(request.getEntityType()).build();
|
return DocumentCreateResponse.builder().resourceId(doc.getId()).resourceIdentifier(request.getEntityType()).build();
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
_ _ _____ _ _
|
|
||||||
/ \ _ __ __ _ ___| |__ ___ | ___(_)_ __ ___ _ __ __ _ ___| |_
|
|
||||||
/ _ \ | '_ \ / _` |/ __| '_ \ / _ \ | |_ | | '_ \ / _ \ '__/ _` |/ __| __|
|
|
||||||
/ ___ \| |_) | (_| | (__| | | | __/ | _| | | | | | __/ | | (_| | (__| |_
|
|
||||||
/_/ \_\ .__/ \__,_|\___|_| |_|\___| |_| |_|_| |_|\___|_| \__,_|\___|\__|
|
|
||||||
|_|
|
|
||||||
|
|
||||||
[2m[37m(c) 2015-2024 Apache Fineract (https://fineract.apache.org)[30m
|
|
||||||
|
|
||||||
[2m[37mPowered by Spring Boot 3.5.6[30m
|
|
||||||
|
|
||||||
|
|
||||||
[2m2026-05-29 19:17:05.618[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.apache.fineract.ServerApplication [0;39m [2m:[0;39m 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)
|
|
||||||
[2m2026-05-29 19:17:05.622[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.apache.fineract.ServerApplication [0;39m [2m:[0;39m No active profile set, falling back to 1 default profile: "default"
|
|
||||||
[2m2026-05-29 19:17:10.836[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Multiple Spring Data modules found, entering strict repository configuration mode
|
|
||||||
[2m2026-05-29 19:17:10.836[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data JPA repositories in DEFAULT mode.
|
|
||||||
[2m2026-05-29 19:17:11.794[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:11.797[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:11.811[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:11.813[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:11.815[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:11.887[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:13.120[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 2267 ms. Found 224 JPA repository interfaces.
|
|
||||||
[2m2026-05-29 19:17:13.130[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.f.s.DefaultListableBeanFactory [0;39m [2m:[0;39m 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]
|
|
||||||
[2m2026-05-29 19:17:13.204[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.f.s.DefaultListableBeanFactory [0;39m [2m:[0;39m 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]]
|
|
||||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobRepository already defined in the application context, skipping the registration of a jobRepository
|
|
||||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobExplorer already defined in the application context, skipping the registration of a jobExplorer
|
|
||||||
[2m2026-05-29 19:17:13.206[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Bean jobLauncher already defined in the application context, skipping the registration of a jobLauncher
|
|
||||||
[2m2026-05-29 19:17:13.208[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.c.c.annotation.BatchRegistrar [0;39m [2m:[0;39m Finished Spring Batch infrastructure beans configuration in 3 ms.
|
|
||||||
[2m2026-05-29 19:17:14.107[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Multiple Spring Data modules found, entering strict repository configuration mode
|
|
||||||
[2m2026-05-29 19:17:14.108[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
|
|
||||||
[2m2026-05-29 19:17:14.408[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.409[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.409[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.410[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.412[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.413[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.414[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.415[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.416[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.417[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.419[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.419[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.420[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.421[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.422[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.423[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.424[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.425[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.425[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.426[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.427[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.428[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.429[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.430[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.430[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.431[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.432[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.433[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.434[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.435[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.438[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.439[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.440[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.441[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.441[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.442[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.444[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.444[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.445[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.445[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.446[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.447[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.448[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.449[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.450[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.452[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.453[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.454[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.455[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.456[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.457[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.458[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.459[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.462[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.464[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.466[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.467[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.468[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.468[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.470[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.470[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.471[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.472[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.473[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.RepositoryConfigurationExtensionSupport[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:14.768[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 657 ms. Found 6 JDBC repository interfaces.
|
|
||||||
[2m2026-05-29 19:17:16.886[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mfaultConfiguringBeanFactoryPostProcessor[0;39m [2m:[0;39m No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
|
|
||||||
[2m2026-05-29 19:17:16.895[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mfaultConfiguringBeanFactoryPostProcessor[0;39m [2m:[0;39m No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created.
|
|
||||||
[2m2026-05-29 19:17:18.104[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.117[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.335[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36morg.reflections.Reflections [0;39m [2m:[0;39m Reflections took 7 ms to scan 1 urls, producing 0 keys and 0 values
|
|
||||||
[2m2026-05-29 19:17:18.339[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.f.i.core.config.cache.CacheConfig [0;39m [2m:[0;39m The following cache configurations are defined but cache does not exists: [userTFAccessToken]
|
|
||||||
[2m2026-05-29 19:17:18.343[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.354[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.360[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.364[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.407[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:18.415[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mtrationDelegate$BeanPostProcessorChecker[0;39m [2m:[0;39m 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.
|
|
||||||
[2m2026-05-29 19:17:19.255[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.w.embedded.tomcat.TomcatWebServer [0;39m [2m:[0;39m Tomcat initialized with port 8443 (https)
|
|
||||||
[2m2026-05-29 19:17:19.278[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Starting service [Tomcat]
|
|
||||||
[2m2026-05-29 19:17:19.279[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardEngine [0;39m [2m:[0;39m Starting Servlet engine: [Apache Tomcat/10.1.49]
|
|
||||||
[2m2026-05-29 19:17:19.328[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.c.c.C.[.[.[/fineract-provider] [0;39m [2m:[0;39m Initializing Spring embedded WebApplicationContext
|
|
||||||
[2m2026-05-29 19:17:19.329[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mw.s.c.ServletWebServerApplicationContext[0;39m [2m:[0;39m Root WebApplicationContext: initialization completed in 13673 ms
|
|
||||||
[2m2026-05-29 19:17:19.577[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Starting...
|
|
||||||
[2m2026-05-29 19:17:19.860[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.pool.HikariPool [0;39m [2m:[0;39m HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@5917b984
|
|
||||||
[2m2026-05-29 19:17:19.861[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Start completed.
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrading tenant store DB at localhost:5432
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.username: postgres
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.password: ****
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.parameters:
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.timezone: Asia/Kolkata
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.description: Default Demo Tenant
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.identifier: default
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - fineract.tenant.name: fineract_default
|
|
||||||
[2m2026-05-29 19:17:20.050[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m - liquibase.analytics.enabled: false
|
|
||||||
[2m2026-05-29 19:17:25.487[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Tenant store upgrade finished
|
|
||||||
[2m2026-05-29 19:17:25.487[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrading all tenants
|
|
||||||
[2m2026-05-29 19:17:25.501[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m Upgrade for tenant default has started
|
|
||||||
[2m2026-05-29 19:17:25.842[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Starting...
|
|
||||||
[2m2026-05-29 19:17:25.946[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.pool.HikariPool [0;39m [2m:[0;39m HikariPool-2 - Added connection org.postgresql.jdbc.PgConnection@625805c1
|
|
||||||
[2m2026-05-29 19:17:25.946[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Start completed.
|
|
||||||
[2m2026-05-29 19:17:31.700[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mliquibase.changelog [0;39m [2m:[0;39m 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
|
|
||||||
|
|
||||||
[2m2026-05-29 19:17:31.748[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Shutdown initiated...
|
|
||||||
[2m2026-05-29 19:17:31.750[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ default] [2m---[0;39m [2m[lTaskExecutor-1][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-2 - Shutdown completed.
|
|
||||||
[2m2026-05-29 19:17:31.752[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36ma.f.i.c.s.m.TenantDatabaseUpgradeService[0;39m [2m:[0;39m 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
|
|
||||||
|
|
||||||
[2m2026-05-29 19:17:31.761[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.b.web.embedded.tomcat.TomcatStarter [0;39m [2m:[0;39m 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
|
|
||||||
[2m2026-05-29 19:17:31.812[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.apache.catalina.core.StandardService [0;39m [2m:[0;39m Stopping service [Tomcat]
|
|
||||||
[2m2026-05-29 19:17:31.817[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.a.c.loader.WebappClassLoaderBase [0;39m [2m:[0;39m 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)
|
|
||||||
[2m2026-05-29 19:17:31.820[0;39m [33m- WARN[0;39m [35m7760[0;39m [33m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mConfigServletWebServerApplicationContext[0;39m [2m:[0;39m Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server
|
|
||||||
[2m2026-05-29 19:17:31.820[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Shutdown initiated...
|
|
||||||
[2m2026-05-29 19:17:31.822[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mcom.zaxxer.hikari.HikariDataSource [0;39m [2m:[0;39m HikariPool-1 - Shutdown completed.
|
|
||||||
[2m2026-05-29 19:17:31.847[0;39m [32m- INFO[0;39m [35m7760[0;39m [32m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36m.s.b.a.l.ConditionEvaluationReportLogger[0;39m [2m:[0;39m
|
|
||||||
|
|
||||||
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
|
|
||||||
[2m2026-05-29 19:17:31.875[0;39m [31m-ERROR[0;39m [35m7760[0;39m [31m[0;39m [ no-tenant] [2m---[0;39m [2m[ main][0;39m [36mo.s.boot.SpringApplication [0;39m [2m:[0;39m 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
|
|
||||||
@@ -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.
+1
-1
@@ -75,7 +75,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Path("/v1/journalentries")
|
@Path("/v1/journalentries")
|
||||||
@Component
|
@Component
|
||||||
@Tag(name = "Journal Entries", description = """
|
@Tag(name = "Journal Entries", description = """
|
||||||
A journal entry refers to the logging of transactions against general ledger accounts. A journal entry may consist of several line items, each of which is either a "debit" or a "credit". The total amount of the debits must equal the total amount of the credits or the journal entry is said to be "unbalanced"\s
|
A journal entry refers to the logging of transactions against general ledger accounts. A journal entry may consist of several line items, each of which is either a "debit" or a "credit". The total amount of the debits must equal the total amount of the credits or the journal entry is said to be "unbalanced"
|
||||||
|
|
||||||
A journal entry directly changes the account balances on the general ledger""")
|
A journal entry directly changes the account balances on the general ledger""")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
+2
-1
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.email.service;
|
|||||||
import java.time.Year;
|
import java.time.Year;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EmailTemplateServiceImpl implements EmailTemplateService {
|
public class EmailTemplateServiceImpl implements EmailTemplateService {
|
||||||
@@ -129,7 +130,7 @@ public class EmailTemplateServiceImpl implements EmailTemplateService {
|
|||||||
+ escape(supportEmail) + "</a>"
|
+ escape(supportEmail) + "</a>"
|
||||||
+ " </p>"
|
+ " </p>"
|
||||||
+ " <p style=\"margin:0;font-size:12px;color:#94a3b8;\">"
|
+ " <p style=\"margin:0;font-size:12px;color:#94a3b8;\">"
|
||||||
+ " © " + Year.now().getValue() + " " + escape(brandName) + ". All rights reserved."
|
+ " © " + Year.now(DateUtils.getDateTimeZoneOfTenant()).getValue() + " " + escape(brandName) + ". All rights reserved."
|
||||||
+ " </p>"
|
+ " </p>"
|
||||||
+ " </td></tr>"
|
+ " </td></tr>"
|
||||||
+ " </table>"
|
+ " </table>"
|
||||||
|
|||||||
+1
@@ -109,6 +109,7 @@ public class ImpsCryptoUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Decrypt inbound payload using the CBS private key. */
|
/** Decrypt inbound payload using the CBS private key. */
|
||||||
|
@SuppressWarnings("StringSplitter")
|
||||||
public String decryptJson(String encryptedPayload) {
|
public String decryptJson(String encryptedPayload) {
|
||||||
if (cbsPrivateKey == null) {
|
if (cbsPrivateKey == null) {
|
||||||
throw new IllegalStateException("CBS private key not loaded — check fineract.imps.cbs.private-key-path");
|
throw new IllegalStateException("CBS private key not loaded — check fineract.imps.cbs.private-key-path");
|
||||||
|
|||||||
+2
-1
@@ -34,6 +34,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* STUB implementation — returns hard-coded SUCCESS for every request and
|
* STUB implementation — returns hard-coded SUCCESS for every request and
|
||||||
@@ -108,7 +109,7 @@ public class ImpsCbsServiceImpl implements ImpsCbsService {
|
|||||||
resp.setRefId(request.getRefId());
|
resp.setRefId(request.getRefId());
|
||||||
resp.setUtr(request.getUtr());
|
resp.setUtr(request.getUtr());
|
||||||
resp.setAmount(request.getAmount());
|
resp.setAmount(request.getAmount());
|
||||||
resp.setTimeStamp(OffsetDateTime.now().format(ISO_OFFSET));
|
resp.setTimeStamp(OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).format(ISO_OFFSET));
|
||||||
resp.setStatus("SUCCESS");
|
resp.setStatus("SUCCESS");
|
||||||
resp.setResponseCode("00");
|
resp.setResponseCode("00");
|
||||||
AccountData payeeAcc = Optional.ofNullable(request.getPayee()).map(PartyData::getAccount).orElse(null);
|
AccountData payeeAcc = Optional.ofNullable(request.getPayee()).map(PartyData::getAccount).orElse(null);
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.Credentials;
|
import okhttp3.Credentials;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
@@ -42,6 +41,7 @@ import org.apache.fineract.infrastructure.openproject.data.OpenProjectProjectDat
|
|||||||
import org.apache.fineract.infrastructure.openproject.data.OpenProjectStatusSummaryData;
|
import org.apache.fineract.infrastructure.openproject.data.OpenProjectStatusSummaryData;
|
||||||
import org.apache.fineract.infrastructure.openproject.data.OpenProjectWorkPackageData;
|
import org.apache.fineract.infrastructure.openproject.data.OpenProjectWorkPackageData;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -164,7 +164,7 @@ public class OpenProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set required custom field: "Last Follow-up Date" (customField8)
|
// Set required custom field: "Last Follow-up Date" (customField8)
|
||||||
body.addProperty("customField8", LocalDate.now().toString());
|
body.addProperty("customField8", LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString());
|
||||||
|
|
||||||
if (typeName != null && !typeName.isBlank()) {
|
if (typeName != null && !typeName.isBlank()) {
|
||||||
JsonObject links = new JsonObject();
|
JsonObject links = new JsonObject();
|
||||||
|
|||||||
+2
-1
@@ -47,6 +47,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -127,7 +128,7 @@ public class RbiFeedFetchService {
|
|||||||
NodeList items = doc.getElementsByTagName("item");
|
NodeList items = doc.getElementsByTagName("item");
|
||||||
log.debug("RBI FeedFetchService - Parsed {} items from RSS for category: {}", items.getLength(), category);
|
log.debug("RBI FeedFetchService - Parsed {} items from RSS for category: {}", items.getLength(), category);
|
||||||
List<RbiFeedItem> newItems = new ArrayList<>();
|
List<RbiFeedItem> newItems = new ArrayList<>();
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
for (int i = 0; i < items.getLength(); i++) {
|
for (int i = 0; i < items.getLength(); i++) {
|
||||||
Element item = (Element) items.item(i);
|
Element item = (Element) items.item(i);
|
||||||
|
|||||||
+3
-1
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.security.otp.api;
|
package org.apache.fineract.infrastructure.security.otp.api;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -107,7 +109,7 @@ public class EmailLoginOtpApiResource {
|
|||||||
}
|
}
|
||||||
final String fwd = req.getHeader("X-Forwarded-For");
|
final String fwd = req.getHeader("X-Forwarded-For");
|
||||||
if (fwd != null && !fwd.isBlank()) {
|
if (fwd != null && !fwd.isBlank()) {
|
||||||
return fwd.split(",")[0].trim();
|
return Iterables.get(Splitter.on(',').split(fwd), 0).trim();
|
||||||
}
|
}
|
||||||
return req.getRemoteAddr();
|
return req.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.security.otp.api;
|
package org.apache.fineract.infrastructure.security.otp.api;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -107,7 +109,7 @@ public class LoginOtpApiResource {
|
|||||||
}
|
}
|
||||||
final String fwd = req.getHeader("X-Forwarded-For");
|
final String fwd = req.getHeader("X-Forwarded-For");
|
||||||
if (fwd != null && !fwd.isBlank()) {
|
if (fwd != null && !fwd.isBlank()) {
|
||||||
return fwd.split(",")[0].trim();
|
return Iterables.get(Splitter.on(',').split(fwd), 0).trim();
|
||||||
}
|
}
|
||||||
return req.getRemoteAddr();
|
return req.getRemoteAddr();
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-16
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
|||||||
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
import org.apache.fineract.infrastructure.security.otp.domain.OtpLog;
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLog;
|
||||||
import org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository;
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.util.OtpDestinationMasker;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.apache.fineract.useradministration.domain.AppUserRepository;
|
import org.apache.fineract.useradministration.domain.AppUserRepository;
|
||||||
import org.apache.fineract.useradministration.mfa.domain.MfaFactorType;
|
import org.apache.fineract.useradministration.mfa.domain.MfaFactorType;
|
||||||
@@ -44,6 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EmailLoginOtpServiceImpl implements EmailLoginOtpService {
|
public class EmailLoginOtpServiceImpl implements EmailLoginOtpService {
|
||||||
@@ -127,17 +129,17 @@ public class EmailLoginOtpServiceImpl implements EmailLoginOtpService {
|
|||||||
|
|
||||||
// Render the branded template — same look and feel for OTP, welcome, password-reset, alerts.
|
// Render the branded template — same look and feel for OTP, welcome, password-reset, alerts.
|
||||||
final String html = emailTemplateService.renderOtp(recipientName, otp, validityMinutes);
|
final String html = emailTemplateService.renderOtp(recipientName, otp, validityMinutes);
|
||||||
final String subject = "Your OTP for Login - " + appName + " (" + LocalDateTime.now().format(SUBJECT_TIMESTAMP) + ")";
|
final String subject = "Your OTP for Login - " + appName + " (" + LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()).format(SUBJECT_TIMESTAMP) + ")";
|
||||||
|
|
||||||
final boolean sent = emailSenderService.sendHtml(email, subject, html);
|
final boolean sent = emailSenderService.sendHtml(email, subject, html);
|
||||||
if (sent) {
|
if (sent) {
|
||||||
log.setStatus(STATUS_SENT);
|
log.setStatus(STATUS_SENT);
|
||||||
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
||||||
LOG.info("Email login OTP sent. otpId={}, user={}, email={}", saved.getId(), username, mask(email));
|
LOG.info("Email login OTP sent. otpId={}, user={}, email={}", saved.getId(), username, OtpDestinationMasker.maskEmail(email));
|
||||||
return OtpGenerateResponse.builder()
|
return OtpGenerateResponse.builder()
|
||||||
.success(true)
|
.success(true)
|
||||||
.otpId(saved.getId())
|
.otpId(saved.getId())
|
||||||
.maskedMobile(mask(email)) // reusing the field; UI just shows where it was sent
|
.maskedMobile(OtpDestinationMasker.maskEmail(email)) // reusing the field; UI just shows where it was sent
|
||||||
.expiresInSeconds(ttlSeconds)
|
.expiresInSeconds(ttlSeconds)
|
||||||
.message("OTP sent to your registered email")
|
.message("OTP sent to your registered email")
|
||||||
.build();
|
.build();
|
||||||
@@ -240,19 +242,6 @@ public class EmailLoginOtpServiceImpl implements EmailLoginOtpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mask(final String email) {
|
|
||||||
if (email == null || !email.contains("@")) {
|
|
||||||
return "****";
|
|
||||||
}
|
|
||||||
final int at = email.indexOf('@');
|
|
||||||
final String local = email.substring(0, at);
|
|
||||||
final String domain = email.substring(at);
|
|
||||||
if (local.length() <= 2) {
|
|
||||||
return "*" + local.substring(local.length() - 1) + domain;
|
|
||||||
}
|
|
||||||
return local.charAt(0) + "***" + local.charAt(local.length() - 1) + domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String displayName(final AppUser user) {
|
private String displayName(final AppUser user) {
|
||||||
if (user.getFirstname() != null && !user.getFirstname().isBlank()) {
|
if (user.getFirstname() != null && !user.getFirstname().isBlank()) {
|
||||||
return user.getFirstname();
|
return user.getFirstname();
|
||||||
|
|||||||
+4
-10
@@ -31,6 +31,7 @@ import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
|||||||
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
import org.apache.fineract.infrastructure.security.otp.domain.OtpLog;
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLog;
|
||||||
import org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository;
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.util.OtpDestinationMasker;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.apache.fineract.useradministration.domain.AppUserRepository;
|
import org.apache.fineract.useradministration.domain.AppUserRepository;
|
||||||
import org.apache.fineract.useradministration.mfa.domain.MfaFactorType;
|
import org.apache.fineract.useradministration.mfa.domain.MfaFactorType;
|
||||||
@@ -156,12 +157,12 @@ public class LoginOtpServiceImpl implements LoginOtpService {
|
|||||||
log.setOtpReference(extractField(response, "reference_id", "refNumber", "ref_number",
|
log.setOtpReference(extractField(response, "reference_id", "refNumber", "ref_number",
|
||||||
"transactionId", "txnId", "requestId"));
|
"transactionId", "txnId", "requestId"));
|
||||||
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
||||||
LOG.info("LincPay login OTP sent. otpId={}, mobile={}", saved.getId(), mask(mobileNumber));
|
LOG.info("LincPay login OTP sent. otpId={}, mobile={}", saved.getId(), OtpDestinationMasker.maskMobile(mobileNumber));
|
||||||
return OtpGenerateResponse.builder()
|
return OtpGenerateResponse.builder()
|
||||||
.success(true)
|
.success(true)
|
||||||
.otpId(saved.getId())
|
.otpId(saved.getId())
|
||||||
.otpReference(saved.getOtpReference())
|
.otpReference(saved.getOtpReference())
|
||||||
.maskedMobile(mask(mobileNumber))
|
.maskedMobile(OtpDestinationMasker.maskMobile(mobileNumber))
|
||||||
.expiresInSeconds(ttlSeconds)
|
.expiresInSeconds(ttlSeconds)
|
||||||
.message("OTP sent")
|
.message("OTP sent")
|
||||||
.build();
|
.build();
|
||||||
@@ -239,7 +240,7 @@ public class LoginOtpServiceImpl implements LoginOtpService {
|
|||||||
row.setVerifiedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
row.setVerifiedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
otpLogRepository.saveAndFlush(row);
|
otpLogRepository.saveAndFlush(row);
|
||||||
autoPromoteFactor(row);
|
autoPromoteFactor(row);
|
||||||
LOG.info("LincPay login OTP verified. otpId={}, mobile={}", row.getId(), mask(row.getMobileNumber()));
|
LOG.info("LincPay login OTP verified. otpId={}, mobile={}", row.getId(), OtpDestinationMasker.maskMobile(row.getMobileNumber()));
|
||||||
return OtpVerifyResponse.builder().verified(true).message("OTP verified").build();
|
return OtpVerifyResponse.builder().verified(true).message("OTP verified").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,13 +365,6 @@ public class LoginOtpServiceImpl implements LoginOtpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mask(final String mobile) {
|
|
||||||
if (mobile == null || mobile.length() < 4) {
|
|
||||||
return "****";
|
|
||||||
}
|
|
||||||
return "******" + mobile.substring(mobile.length() - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String truncate(final String s, final int max) {
|
private String truncate(final String s, final int max) {
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
-2
@@ -20,7 +20,6 @@ package org.apache.fineract.organisation.teller.service;
|
|||||||
|
|
||||||
import jakarta.persistence.PersistenceException;
|
import jakarta.persistence.PersistenceException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
@@ -44,7 +43,6 @@ import org.apache.fineract.organisation.teller.domain.CashierTransactionReposito
|
|||||||
import org.apache.fineract.organisation.teller.domain.CashierTxnType;
|
import org.apache.fineract.organisation.teller.domain.CashierTxnType;
|
||||||
import org.apache.fineract.organisation.teller.domain.Teller;
|
import org.apache.fineract.organisation.teller.domain.Teller;
|
||||||
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
import org.apache.fineract.organisation.teller.domain.TellerRepositoryWrapper;
|
||||||
import org.apache.fineract.organisation.teller.exception.CashierExistForTellerException;
|
|
||||||
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
|
import org.apache.fineract.organisation.teller.exception.CashierNotFoundException;
|
||||||
import org.apache.fineract.organisation.teller.exception.TellerAlreadyExistsException;
|
import org.apache.fineract.organisation.teller.exception.TellerAlreadyExistsException;
|
||||||
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
import org.apache.fineract.organisation.teller.serialization.TellerCommandFromApiJsonDeserializer;
|
||||||
|
|||||||
+7
-4
@@ -60,6 +60,8 @@ public interface ShareAccountApiConstants {
|
|||||||
|
|
||||||
String externalid_paramname = "externalId";
|
String externalid_paramname = "externalId";
|
||||||
|
|
||||||
|
String resolutionnumber_paramname = "resolutionNumber";
|
||||||
|
|
||||||
String currency_paramname = "currencyCode";
|
String currency_paramname = "currencyCode";
|
||||||
|
|
||||||
String digitsafterdecimal_paramname = "digitsAfterDecimal";
|
String digitsafterdecimal_paramname = "digitsAfterDecimal";
|
||||||
@@ -102,9 +104,10 @@ public interface ShareAccountApiConstants {
|
|||||||
|
|
||||||
Set<String> supportedParameters = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(locale_paramname, dateformat_paramname,
|
Set<String> supportedParameters = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(locale_paramname, dateformat_paramname,
|
||||||
id_paramname, clientid_paramname, productid_paramname, submitteddate_paramname, approveddate_paramname, externalid_paramname,
|
id_paramname, clientid_paramname, productid_paramname, submitteddate_paramname, approveddate_paramname, externalid_paramname,
|
||||||
currency_paramname, digitsafterdecimal_paramname, inmultiplesof_paramname, requestedshares_paramname,
|
resolutionnumber_paramname, currency_paramname, digitsafterdecimal_paramname, inmultiplesof_paramname,
|
||||||
savingsaccountid_paramname, lockinperiod_paramname, lockperiodfrequencytype_paramname, minimumactiveperiod_paramname,
|
requestedshares_paramname, savingsaccountid_paramname, lockinperiod_paramname, lockperiodfrequencytype_paramname,
|
||||||
minimumactiveperiodfrequencytype_paramname, allowdividendcalculationforinactiveclients_paramname, charges_paramname,
|
minimumactiveperiod_paramname, minimumactiveperiodfrequencytype_paramname,
|
||||||
applicationdate_param, purchaseddate_paramname, numberofshares_paramname, purchasedprice_paramname)));
|
allowdividendcalculationforinactiveclients_paramname, charges_paramname, applicationdate_param, purchaseddate_paramname,
|
||||||
|
numberofshares_paramname, purchasedprice_paramname)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -42,6 +42,7 @@ public class AccountRequest implements Serializable {
|
|||||||
private Long numberOfShares;
|
private Long numberOfShares;
|
||||||
private String allowDividendCalculationForInactiveClients;
|
private String allowDividendCalculationForInactiveClients;
|
||||||
private String externalId;
|
private String externalId;
|
||||||
|
private String resolutionNumber;
|
||||||
private String minimumActivePeriodFrequencyType;
|
private String minimumActivePeriodFrequencyType;
|
||||||
private Long savingsAccountId;
|
private Long savingsAccountId;
|
||||||
private String locale;
|
private String locale;
|
||||||
|
|||||||
+38
@@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.client.domain.ClientAddress;
|
|||||||
import org.apache.fineract.portfolio.client.domain.ClientAddressRepository;
|
import org.apache.fineract.portfolio.client.domain.ClientAddressRepository;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientAddressRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientAddressRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.client.service.ClientChangeNotificationService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -53,6 +54,7 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
|||||||
private final AddressRepository addressRepository;
|
private final AddressRepository addressRepository;
|
||||||
private final ClientAddressRepositoryWrapper clientAddressRepositoryWrapper;
|
private final ClientAddressRepositoryWrapper clientAddressRepositoryWrapper;
|
||||||
private final AddressCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
private final AddressCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||||
|
private final ClientChangeNotificationService clientChangeNotificationService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult addClientAddress(final Long clientId, final Long addressTypeId, final JsonCommand command) {
|
public CommandProcessingResult addClientAddress(final Long clientId, final Long addressTypeId, final JsonCommand command) {
|
||||||
@@ -170,6 +172,10 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
|||||||
|
|
||||||
final Address addobj = this.addressRepository.getReferenceById(addressId);
|
final Address addobj = this.addressRepository.getReferenceById(addressId);
|
||||||
|
|
||||||
|
// Snapshot before mutation — notification-only (no OTP gate for address; see class Javadoc /
|
||||||
|
// feature plan). Compared against the post-update state below to build the change message.
|
||||||
|
final String oldAddressSummary = formatAddressSummary(addobj);
|
||||||
|
|
||||||
if (!command.stringValueOfParameterNamed("addressLine1").isEmpty()) {
|
if (!command.stringValueOfParameterNamed("addressLine1").isEmpty()) {
|
||||||
|
|
||||||
is_address_update = true;
|
is_address_update = true;
|
||||||
@@ -271,6 +277,38 @@ public class AddressWritePlatformServiceImpl implements AddressWritePlatformServ
|
|||||||
}
|
}
|
||||||
this.clientAddressRepository.saveAndFlush(clientAddressObj);
|
this.clientAddressRepository.saveAndFlush(clientAddressObj);
|
||||||
|
|
||||||
|
if (is_address_update) {
|
||||||
|
final String newAddressSummary = formatAddressSummary(addobj);
|
||||||
|
final Client client = clientAddressObj.getClient();
|
||||||
|
this.clientChangeNotificationService.notifyContactOrAddressChanged(client, ClientChangeNotificationService.FIELD_LABEL_ADDRESS,
|
||||||
|
oldAddressSummary, newAddressSummary, client.getEmailAddress());
|
||||||
|
}
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(clientAddressObj.getId()).build();
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(clientAddressObj.getId()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human-readable, comma-joined summary of the address's free-text fields — used only for the
|
||||||
|
* change-notification email body (see {@link ClientChangeNotificationService}), not persisted.
|
||||||
|
*/
|
||||||
|
private String formatAddressSummary(final Address address) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
appendIfPresent(sb, address.getAddressLine1());
|
||||||
|
appendIfPresent(sb, address.getAddressLine2());
|
||||||
|
appendIfPresent(sb, address.getAddressLine3());
|
||||||
|
appendIfPresent(sb, address.getTownVillage());
|
||||||
|
appendIfPresent(sb, address.getCity());
|
||||||
|
appendIfPresent(sb, address.getCountyDistrict());
|
||||||
|
appendIfPresent(sb, address.getPostalCode());
|
||||||
|
return sb.length() == 0 ? "(not set)" : sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendIfPresent(final StringBuilder sb, final String value) {
|
||||||
|
if (value != null && !value.isBlank()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-5
@@ -33,6 +33,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashexchange")
|
@Path("/v1/cashexchange")
|
||||||
@Component
|
@Component
|
||||||
@@ -94,7 +95,7 @@ public class CashExchangeApiResource {
|
|||||||
header.setNote(note);
|
header.setNote(note);
|
||||||
header.setCustomerIdProof(customerIdProof);
|
header.setCustomerIdProof(customerIdProof);
|
||||||
header.setIsReversed(false);
|
header.setIsReversed(false);
|
||||||
header.setCreatedAt(LocalDateTime.now());
|
header.setCreatedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
@@ -107,7 +108,9 @@ public class CashExchangeApiResource {
|
|||||||
+ "JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
+ "JOIN m_appuser au ON au.staff_id = ms.id WHERE au.id = ?",
|
||||||
Long.class, user.getId());
|
Long.class, user.getId());
|
||||||
header.setCashierId(cashierId);
|
header.setCashierId(cashierId);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// resolving the current user must never block recording the exchange
|
// resolving the current user must never block recording the exchange
|
||||||
@@ -160,14 +163,18 @@ public class CashExchangeApiResource {
|
|||||||
try {
|
try {
|
||||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||||
reason = json.has("reason") && !json.get("reason").isJsonNull() ? json.get("reason").getAsString() : null;
|
reason = json.has("reason") && !json.get("reason").isJsonNull() ? json.get("reason").getAsString() : null;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
exchange.setIsReversed(true);
|
exchange.setIsReversed(true);
|
||||||
exchange.setReversedAt(LocalDateTime.now());
|
exchange.setReversedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
exchange.setReverseReason(reason);
|
exchange.setReverseReason(reason);
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
if (user != null) exchange.setReversedBy(user.getId());
|
if (user != null) exchange.setReversedBy(user.getId());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
repository.save(exchange);
|
repository.save(exchange);
|
||||||
JsonObject result = new JsonObject();
|
JsonObject result = new JsonObject();
|
||||||
result.addProperty("resourceId", id);
|
result.addProperty("resourceId", id);
|
||||||
|
|||||||
+49
-123
@@ -59,6 +59,9 @@ import org.apache.fineract.useradministration.domain.AppUser;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
|
||||||
@Path("/v1/cashier/deposit-requests")
|
@Path("/v1/cashier/deposit-requests")
|
||||||
@Component
|
@Component
|
||||||
@@ -101,6 +104,13 @@ public class CashierDepositRequestApiResource {
|
|||||||
final String bankName = fromJsonHelper.extractStringNamed("bankName", root);
|
final String bankName = fromJsonHelper.extractStringNamed("bankName", root);
|
||||||
final String txnDate = fromJsonHelper.extractStringNamed("txnDate", root);
|
final String txnDate = fromJsonHelper.extractStringNamed("txnDate", root);
|
||||||
final String denominationData = fromJsonHelper.extractStringNamed("denominationData", 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()) {
|
if (accountNo == null || accountNo.isBlank()) {
|
||||||
throw apiError(400, "Account number is required");
|
throw apiError(400, "Account number is required");
|
||||||
@@ -115,6 +125,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
throw apiError(400, "Amount must be greater than zero");
|
throw apiError(400, "Amount must be greater than zero");
|
||||||
}
|
}
|
||||||
validateDenominationTotal(denominationData, amount);
|
validateDenominationTotal(denominationData, amount);
|
||||||
|
validateDdLimit(ddFaceValue);
|
||||||
|
|
||||||
final String activityLog = buildActivityLog(null, "SUBMITTED", currentUser.getUsername(), null);
|
final String activityLog = buildActivityLog(null, "SUBMITTED", currentUser.getUsername(), null);
|
||||||
|
|
||||||
@@ -123,8 +134,9 @@ public class CashierDepositRequestApiResource {
|
|||||||
(account_no, account_type, savings_account_id, gl_account_id, contra_gl_id, office_id, currency_code,
|
(account_no, account_type, savings_account_id, gl_account_id, contra_gl_id, office_id, currency_code,
|
||||||
client_id, client_name, amount,
|
client_id, client_name, amount,
|
||||||
payment_type_id, payment_type_name, description, cheque_no, bank_name, txn_date,
|
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)
|
status, cashier_id, cashier_name, cashier_username, denomination_data, activity_log, submitted_at,
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
dd_face_value, dd_charges, dd_gst_percentage, dd_gst_amount)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
""",
|
""",
|
||||||
Long.class,
|
Long.class,
|
||||||
@@ -135,7 +147,8 @@ public class CashierDepositRequestApiResource {
|
|||||||
txnDate != null ? Date.valueOf(txnDate) : null,
|
txnDate != null ? Date.valueOf(txnDate) : null,
|
||||||
"PENDING",
|
"PENDING",
|
||||||
currentUser.getId(), currentUser.getDisplayName(), currentUser.getUsername(),
|
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
|
// Globally unique voucher number from the central m_voucher register
|
||||||
@@ -189,7 +202,8 @@ public class CashierDepositRequestApiResource {
|
|||||||
+ " amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,"
|
+ " amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,"
|
||||||
+ " txn_date, status, cashier_id, cashier_name, cashier_username,"
|
+ " txn_date, status, cashier_id, cashier_name, cashier_username,"
|
||||||
+ " manager_id, manager_name, manager_username, rejection_reason, txn_id,"
|
+ " 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
|
+ " FROM m_cashier_deposit_request " + where
|
||||||
+ " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
|
+ " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
|
||||||
pageParams.toArray());
|
pageParams.toArray());
|
||||||
@@ -242,7 +256,8 @@ public class CashierDepositRequestApiResource {
|
|||||||
SELECT id, voucher_no, account_no, account_type, client_name, amount,
|
SELECT id, voucher_no, account_no, account_type, client_name, amount,
|
||||||
payment_type_name, description, txn_date, status,
|
payment_type_name, description, txn_date, status,
|
||||||
cashier_name, manager_name, manager_username, rejection_reason, txn_id,
|
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
|
FROM m_cashier_deposit_request
|
||||||
""" + where + " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
|
""" + where + " ORDER BY submitted_at DESC LIMIT ? OFFSET ?",
|
||||||
pageParams.toArray()
|
pageParams.toArray()
|
||||||
@@ -352,7 +367,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
||||||
final Object ptId = record.get("payment_type_id");
|
final Object ptId = record.get("payment_type_id");
|
||||||
final Object txnDateObj = record.get("txn_date");
|
final Object txnDateObj = record.get("txn_date");
|
||||||
final String txnDateStr = txnDateObj != null ? txnDateObj.toString().split(" ")[0] : null;
|
final String txnDateStr = txnDateObj != null ? Iterables.get(Splitter.on(' ').split(txnDateObj.toString()), 0) : null;
|
||||||
final String description = (String) record.get("description");
|
final String description = (String) record.get("description");
|
||||||
|
|
||||||
final String storedAccountType = (String) record.get("account_type");
|
final String storedAccountType = (String) record.get("account_type");
|
||||||
@@ -424,6 +439,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final JsonObject res = new JsonObject();
|
final JsonObject res = new JsonObject();
|
||||||
res.addProperty("resourceId", id);
|
res.addProperty("resourceId", id);
|
||||||
res.addProperty("status", "APPROVED");
|
res.addProperty("status", "APPROVED");
|
||||||
|
res.addProperty("voucherNo", Optional.ofNullable(record.get("voucher_no")).map(Object::toString).orElse(""));
|
||||||
if (txnId != null) res.addProperty("txnId", txnId);
|
if (txnId != null) res.addProperty("txnId", txnId);
|
||||||
return gson.toJson(res);
|
return gson.toJson(res);
|
||||||
}
|
}
|
||||||
@@ -500,7 +516,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
||||||
final Object ptId = record.get("payment_type_id");
|
final Object ptId = record.get("payment_type_id");
|
||||||
final Object txnDateObj = record.get("txn_date");
|
final Object txnDateObj = record.get("txn_date");
|
||||||
final String txnDateStr = txnDateObj != null ? txnDateObj.toString().split(" ")[0] : null;
|
final String txnDateStr = txnDateObj != null ? Iterables.get(Splitter.on(' ').split(txnDateObj.toString()), 0) : null;
|
||||||
final String description = (String) record.get("description");
|
final String description = (String) record.get("description");
|
||||||
|
|
||||||
if (savingsAccountId == null || !isCurrentAccountById(savingsAccountId)) {
|
if (savingsAccountId == null || !isCurrentAccountById(savingsAccountId)) {
|
||||||
@@ -532,7 +548,8 @@ public class CashierDepositRequestApiResource {
|
|||||||
amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,
|
amount, payment_type_id, payment_type_name, description, cheque_no, bank_name,
|
||||||
txn_date, status, cashier_id, cashier_name, cashier_username,
|
txn_date, status, cashier_id, cashier_name, cashier_username,
|
||||||
manager_id, manager_name, manager_username, rejection_reason, txn_id,
|
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 = ?
|
FROM m_cashier_deposit_request WHERE id = ?
|
||||||
""", id);
|
""", id);
|
||||||
if (rows.isEmpty()) {
|
if (rows.isEmpty()) {
|
||||||
@@ -545,7 +562,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final Long savingsAccountId, final BigDecimal amount,
|
final Long savingsAccountId, final BigDecimal amount,
|
||||||
final Object paymentTypeId, final String txnDateStr, final String note) {
|
final Object paymentTypeId, final String txnDateStr, final String note) {
|
||||||
|
|
||||||
final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
||||||
|
|
||||||
final JsonObject payload = new JsonObject();
|
final JsonObject payload = new JsonObject();
|
||||||
@@ -573,7 +590,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final Long currentAccountId, final BigDecimal amount,
|
final Long currentAccountId, final BigDecimal amount,
|
||||||
final Object paymentTypeId, final String txnDateStr, final String note) {
|
final Object paymentTypeId, final String txnDateStr, final String note) {
|
||||||
|
|
||||||
final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
||||||
|
|
||||||
final JsonObject payload = new JsonObject();
|
final JsonObject payload = new JsonObject();
|
||||||
@@ -630,7 +647,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final Object paymentTypeId, final String txnDateStr, final String note,
|
final Object paymentTypeId, final String txnDateStr, final String note,
|
||||||
final boolean isDeposit) {
|
final boolean isDeposit) {
|
||||||
|
|
||||||
final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
||||||
|
|
||||||
// Deposit (CR): cashier receives cash → credit selected GL, debit cash vault (contra)
|
// Deposit (CR): cashier receives cash → credit selected GL, debit cash vault (contra)
|
||||||
@@ -672,67 +689,6 @@ public class CashierDepositRequestApiResource {
|
|||||||
return result.getTransactionId() != null ? result.getTransactionId() : null;
|
return result.getTransactionId() != null ? result.getTransactionId() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveGlDenomination(final String denominationData,
|
|
||||||
final Map<?, ?> record, final String txnIdRaw) {
|
|
||||||
final JsonObject denomMap;
|
|
||||||
try {
|
|
||||||
denomMap = gson.fromJson(denominationData, JsonObject.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (denomMap == null || denomMap.size() == 0) return;
|
|
||||||
|
|
||||||
// Convert hex transaction ID to Long (same logic as CashierGlDenominationApiResource)
|
|
||||||
Long txnId = null;
|
|
||||||
if (txnIdRaw != null && !txnIdRaw.isBlank()) {
|
|
||||||
try {
|
|
||||||
txnId = Long.valueOf(txnIdRaw);
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
try {
|
|
||||||
final String hex = txnIdRaw.length() > 15 ? txnIdRaw.substring(txnIdRaw.length() - 15) : txnIdRaw;
|
|
||||||
txnId = Long.parseUnsignedLong(hex, 16);
|
|
||||||
} catch (NumberFormatException ignored2) {
|
|
||||||
txnId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final String accountNo = (String) record.get("account_no");
|
|
||||||
final Object cashierIdObj = record.get("cashier_id");
|
|
||||||
final Long cashierId = cashierIdObj != null ? Long.valueOf(cashierIdObj.toString()) : null;
|
|
||||||
final String cashierUsername = (String) record.get("cashier_username");
|
|
||||||
|
|
||||||
for (final Map.Entry<String, com.google.gson.JsonElement> entry : denomMap.entrySet()) {
|
|
||||||
final int count;
|
|
||||||
try { count = entry.getValue().getAsInt(); } catch (Exception e) { continue; }
|
|
||||||
if (count <= 0) continue;
|
|
||||||
|
|
||||||
final String[] parts = entry.getKey().split(":", 2);
|
|
||||||
if (parts.length != 2) continue;
|
|
||||||
final String kind = parts[0];
|
|
||||||
final int value;
|
|
||||||
try { value = Integer.parseInt(parts[1]); } catch (Exception e) { continue; }
|
|
||||||
|
|
||||||
final List<Long> ids = jdbcTemplate.queryForList(
|
|
||||||
"SELECT id FROM m_denomination_master WHERE denomination = ? AND kind = ?"
|
|
||||||
+ " AND (deleted IS NULL OR deleted = false) LIMIT 1",
|
|
||||||
Long.class, value, kind);
|
|
||||||
if (ids.isEmpty()) continue;
|
|
||||||
final Long denomId = ids.get(0);
|
|
||||||
|
|
||||||
jdbcTemplate.update("""
|
|
||||||
INSERT INTO m_savings_txn_denomination
|
|
||||||
(txn_id, savings_account_id, account_no, client_id, denomination_id,
|
|
||||||
denomination_value, count, total_amount, txn_date, txn_type, officer_id, officer_username)
|
|
||||||
VALUES (?, NULL, ?, NULL, ?, ?, ?, ?, CURRENT_DATE, 'DEPOSIT', ?, ?)
|
|
||||||
""",
|
|
||||||
txnId, accountNo, denomId,
|
|
||||||
value, count, BigDecimal.valueOf((long) value * count),
|
|
||||||
cashierId, cashierUsername
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the denomination breakdown the cashier entered at submit time into
|
* Saves the denomination breakdown the cashier entered at submit time into
|
||||||
* m_savings_txn_denomination once the deposit is approved, so the cashier's
|
* m_savings_txn_denomination once the deposit is approved, so the cashier's
|
||||||
@@ -783,56 +739,6 @@ public class CashierDepositRequestApiResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveDepositDenomination(final String denominationData,
|
|
||||||
final Map<?, ?> record, final Long txnId) {
|
|
||||||
final JsonObject denomMap;
|
|
||||||
try {
|
|
||||||
denomMap = gson.fromJson(denominationData, JsonObject.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return; // malformed JSON — skip silently
|
|
||||||
}
|
|
||||||
if (denomMap == null || denomMap.size() == 0) return;
|
|
||||||
|
|
||||||
final Object saObj = record.get("savings_account_id");
|
|
||||||
final Long savingsAccountId = saObj != null ? Long.valueOf(saObj.toString()) : null;
|
|
||||||
final String accountNo = (String) record.get("account_no");
|
|
||||||
final Object clientIdObj = record.get("client_id");
|
|
||||||
final Long clientId = clientIdObj != null ? Long.valueOf(clientIdObj.toString()) : null;
|
|
||||||
final Object cashierIdObj = record.get("cashier_id");
|
|
||||||
final Long cashierId = cashierIdObj != null ? Long.valueOf(cashierIdObj.toString()) : null;
|
|
||||||
final String cashierUsername = (String) record.get("cashier_username");
|
|
||||||
|
|
||||||
for (final Map.Entry<String, JsonElement> entry : denomMap.entrySet()) {
|
|
||||||
final int count;
|
|
||||||
try { count = entry.getValue().getAsInt(); } catch (Exception e) { continue; }
|
|
||||||
if (count <= 0) continue;
|
|
||||||
|
|
||||||
final String[] parts = entry.getKey().split(":", 2); // "note:500" or "coin:1"
|
|
||||||
if (parts.length != 2) continue;
|
|
||||||
final String kind = parts[0];
|
|
||||||
final int value;
|
|
||||||
try { value = Integer.parseInt(parts[1]); } catch (Exception e) { continue; }
|
|
||||||
|
|
||||||
final List<Long> ids = jdbcTemplate.queryForList(
|
|
||||||
"SELECT id FROM m_denomination_master WHERE denomination = ? AND kind = ?"
|
|
||||||
+ " AND (deleted IS NULL OR deleted = false) LIMIT 1",
|
|
||||||
Long.class, value, kind);
|
|
||||||
if (ids.isEmpty()) continue;
|
|
||||||
final Long denomId = ids.get(0);
|
|
||||||
|
|
||||||
jdbcTemplate.update("""
|
|
||||||
INSERT INTO m_savings_txn_denomination
|
|
||||||
(txn_id, savings_account_id, account_no, client_id, denomination_id,
|
|
||||||
denomination_value, count, total_amount, txn_date, txn_type, officer_id, officer_username)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_DATE, 'DEPOSIT', ?, ?)
|
|
||||||
""",
|
|
||||||
txnId, savingsAccountId, accountNo, clientId, denomId,
|
|
||||||
value, count, BigDecimal.valueOf((long) value * count),
|
|
||||||
cashierId, cashierUsername
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a cash denomination breakdown is provided, its total must exactly equal
|
* If a cash denomination breakdown is provided, its total must exactly equal
|
||||||
* the deposit amount — prevents the recorded vault movement from drifting
|
* the deposit amount — prevents the recorded vault movement from drifting
|
||||||
@@ -869,6 +775,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.
|
* Checker authorization: only manager-level users may approve or reject.
|
||||||
* Allowed: super users (ALL_FUNCTIONS), or any role whose name contains
|
* Allowed: super users (ALL_FUNCTIONS), or any role whose name contains
|
||||||
@@ -912,7 +838,7 @@ public class CashierDepositRequestApiResource {
|
|||||||
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||||
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-6
@@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.cashintercashier.domain.CashInterCashierRep
|
|||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashintercashier")
|
@Path("/v1/cashintercashier")
|
||||||
@Component
|
@Component
|
||||||
@@ -69,7 +70,7 @@ public class CashInterCashierApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final LocalDate transferDate = json.has("transferDate") && !json.get("transferDate").isJsonNull()
|
final LocalDate transferDate = json.has("transferDate") && !json.get("transferDate").isJsonNull()
|
||||||
? LocalDate.parse(json.get("transferDate").getAsString()) : LocalDate.now();
|
? LocalDate.parse(json.get("transferDate").getAsString()) : LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
||||||
? json.get("fromParty").getAsString() : null;
|
? json.get("fromParty").getAsString() : null;
|
||||||
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
||||||
@@ -95,13 +96,15 @@ public class CashInterCashierApiResource {
|
|||||||
header.setToParty(toParty);
|
header.setToParty(toParty);
|
||||||
header.setTransferAmount(total);
|
header.setTransferAmount(total);
|
||||||
header.setNote(note);
|
header.setNote(note);
|
||||||
header.setCreatedAt(LocalDateTime.now());
|
header.setCreatedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
header.setIsReversed(false);
|
header.setIsReversed(false);
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
header.setPerformedBy(user.getId());
|
header.setPerformedBy(user.getId());
|
||||||
header.setPerformedByUsername(user.getUsername());
|
header.setPerformedByUsername(user.getUsername());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
CashInterCashier saved = repository.saveAndFlush(header);
|
CashInterCashier saved = repository.saveAndFlush(header);
|
||||||
saveDetails(saved.getId(), denominations);
|
saveDetails(saved.getId(), denominations);
|
||||||
|
|
||||||
@@ -128,13 +131,17 @@ public class CashInterCashierApiResource {
|
|||||||
try {
|
try {
|
||||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
AppUser user = null;
|
AppUser user = null;
|
||||||
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {}
|
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
record.setIsReversed(true);
|
record.setIsReversed(true);
|
||||||
record.setReversedAt(LocalDateTime.now());
|
record.setReversedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
record.setReverseReason(reason);
|
record.setReverseReason(reason);
|
||||||
if (user != null) record.setReversedBy(user.getId());
|
if (user != null) record.setReversedBy(user.getId());
|
||||||
repository.save(record);
|
repository.save(record);
|
||||||
|
|||||||
+9
-6
@@ -32,6 +32,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashreconciliation")
|
@Path("/v1/cashreconciliation")
|
||||||
@Component
|
@Component
|
||||||
@@ -62,7 +63,7 @@ public class CashReconciliationApiResource {
|
|||||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||||
"cashierId query param is required.");
|
"cashierId query param is required.");
|
||||||
}
|
}
|
||||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
|
|
||||||
// In: vault → cashier allocations — GROUP BY denomination_id+kind to keep coin and note separate
|
// In: vault → cashier allocations — GROUP BY denomination_id+kind to keep coin and note separate
|
||||||
final String sqlIn1 = "SELECT ctd.denomination_id, ctd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ctd.count) AS total_count "
|
final String sqlIn1 = "SELECT ctd.denomination_id, ctd.denomination_value, COALESCE(dm.kind, 'note') AS kind, SUM(ctd.count) AS total_count "
|
||||||
@@ -187,7 +188,7 @@ public class CashReconciliationApiResource {
|
|||||||
@Path("expected/me")
|
@Path("expected/me")
|
||||||
public String getExpectedForMe(@QueryParam("asOfDate") String asOfDate) {
|
public String getExpectedForMe(@QueryParam("asOfDate") String asOfDate) {
|
||||||
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
context.authenticatedUser().validateHasPermissionTo("READ_CASHRECONCILIATION");
|
||||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
Long appUserId;
|
Long appUserId;
|
||||||
try {
|
try {
|
||||||
appUserId = context.authenticatedUser().getId();
|
appUserId = context.authenticatedUser().getId();
|
||||||
@@ -223,7 +224,7 @@ public class CashReconciliationApiResource {
|
|||||||
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
throw new PlatformDataIntegrityException("error.msg.cashreconciliation.cashier.required",
|
||||||
"cashierId query param is required.");
|
"cashierId query param is required.");
|
||||||
}
|
}
|
||||||
final String reconDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
final String reconDate = (date != null && !date.isBlank()) ? date : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
Integer count = jdbcTemplate.queryForObject(
|
Integer count = jdbcTemplate.queryForObject(
|
||||||
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
"SELECT COUNT(*) FROM m_cash_reconciliation WHERE cashier_id = ? AND recon_date = CAST(? AS DATE)",
|
||||||
Integer.class, cashierId, reconDate);
|
Integer.class, cashierId, reconDate);
|
||||||
@@ -247,7 +248,7 @@ public class CashReconciliationApiResource {
|
|||||||
"cashierId is required.");
|
"cashierId is required.");
|
||||||
}
|
}
|
||||||
final LocalDate reconDate = json.has("reconDate") && !json.get("reconDate").isJsonNull()
|
final LocalDate reconDate = json.has("reconDate") && !json.get("reconDate").isJsonNull()
|
||||||
? LocalDate.parse(json.get("reconDate").getAsString()) : LocalDate.now();
|
? LocalDate.parse(json.get("reconDate").getAsString()) : LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String cashierName = json.has("cashierName") && !json.get("cashierName").isJsonNull()
|
final String cashierName = json.has("cashierName") && !json.get("cashierName").isJsonNull()
|
||||||
? json.get("cashierName").getAsString() : null;
|
? json.get("cashierName").getAsString() : null;
|
||||||
final Long tellerId = json.has("tellerId") && !json.get("tellerId").isJsonNull()
|
final Long tellerId = json.has("tellerId") && !json.get("tellerId").isJsonNull()
|
||||||
@@ -299,12 +300,14 @@ public class CashReconciliationApiResource {
|
|||||||
header.setVariance(variance);
|
header.setVariance(variance);
|
||||||
header.setStatus(status);
|
header.setStatus(status);
|
||||||
header.setNote(note);
|
header.setNote(note);
|
||||||
header.setCreatedAt(LocalDateTime.now());
|
header.setCreatedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
header.setPerformedBy(user.getId());
|
header.setPerformedBy(user.getId());
|
||||||
header.setPerformedByUsername(user.getUsername());
|
header.setPerformedByUsername(user.getUsername());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
CashReconciliation saved = repository.saveAndFlush(header);
|
CashReconciliation saved = repository.saveAndFlush(header);
|
||||||
|
|
||||||
for (CashReconciliationDetail d : details) {
|
for (CashReconciliationDetail d : details) {
|
||||||
|
|||||||
+19
-10
@@ -18,6 +18,7 @@ import org.apache.fineract.portfolio.cashreport.service.CashierRoleHelper;
|
|||||||
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
import org.apache.fineract.portfolio.denominationmaster.service.DenominationBreakdownWriter;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashreports")
|
@Path("/v1/cashreports")
|
||||||
@Component
|
@Component
|
||||||
@@ -34,7 +35,7 @@ public class CashReportApiResource {
|
|||||||
@GET
|
@GET
|
||||||
@Path("closing")
|
@Path("closing")
|
||||||
public String closing(@QueryParam("date") String date) {
|
public String closing(@QueryParam("date") String date) {
|
||||||
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
|
|
||||||
// Single query — conditional aggregation for deposit, withdrawal, net per denomination
|
// Single query — conditional aggregation for deposit, withdrawal, net per denomination
|
||||||
final String sql = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, "
|
final String sql = "SELECT std.denomination_id, std.denomination_value, COALESCE(dm.kind, 'note') AS kind, "
|
||||||
@@ -238,7 +239,7 @@ public class CashReportApiResource {
|
|||||||
return gson.toJson(result);
|
return gson.toJson(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String today = LocalDate.now().toString();
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
|
|
||||||
// Find the earliest transfer date before today for which no settlement exists on or after that date
|
// Find the earliest transfer date before today for which no settlement exists on or after that date
|
||||||
final String sql = "SELECT MIN(ct.transfer_date) AS earliest_unsettled "
|
final String sql = "SELECT MIN(ct.transfer_date) AS earliest_unsettled "
|
||||||
@@ -256,7 +257,9 @@ public class CashReportApiResource {
|
|||||||
java.sql.Date earliestDate = null;
|
java.sql.Date earliestDate = null;
|
||||||
try {
|
try {
|
||||||
earliestDate = jdbcTemplate.queryForObject(sql, java.sql.Date.class, cashierId, today, cashierId);
|
earliestDate = jdbcTemplate.queryForObject(sql, java.sql.Date.class, cashierId, today, cashierId);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
final boolean unsettled = earliestDate != null;
|
final boolean unsettled = earliestDate != null;
|
||||||
final String unsettledSince = earliestDate != null ? earliestDate.toString() : null;
|
final String unsettledSince = earliestDate != null ? earliestDate.toString() : null;
|
||||||
@@ -265,7 +268,9 @@ public class CashReportApiResource {
|
|||||||
if (unsettled) {
|
if (unsettled) {
|
||||||
try {
|
try {
|
||||||
pendingAmount = denominationBreakdownWriter.getCashierTotalCash(cashierId);
|
pendingAmount = denominationBreakdownWriter.getCashierTotalCash(cashierId);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether today's morning cash transfer has been received
|
// Check whether today's morning cash transfer has been received
|
||||||
@@ -276,7 +281,9 @@ public class CashReportApiResource {
|
|||||||
Integer todayTransferCount = 0;
|
Integer todayTransferCount = 0;
|
||||||
try {
|
try {
|
||||||
todayTransferCount = jdbcTemplate.queryForObject(morningTransferSql, Integer.class, cashierId, today);
|
todayTransferCount = jdbcTemplate.queryForObject(morningTransferSql, Integer.class, cashierId, today);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
final boolean morningTransferReceived = todayTransferCount != null && todayTransferCount > 0;
|
final boolean morningTransferReceived = todayTransferCount != null && todayTransferCount > 0;
|
||||||
|
|
||||||
result.put("cashierId", cashierId);
|
result.put("cashierId", cashierId);
|
||||||
@@ -295,7 +302,7 @@ public class CashReportApiResource {
|
|||||||
@Path("cashier-position")
|
@Path("cashier-position")
|
||||||
public String cashierPosition(@QueryParam("cashierId") Long cashierId,
|
public String cashierPosition(@QueryParam("cashierId") Long cashierId,
|
||||||
@QueryParam("asOfDate") String asOfDate) {
|
@QueryParam("asOfDate") String asOfDate) {
|
||||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
|
|
||||||
// CASHIER role: always scope to their own position only
|
// CASHIER role: always scope to their own position only
|
||||||
final Long scopedId = cashierRoleHelper.getCashierIdForFiltering();
|
final Long scopedId = cashierRoleHelper.getCashierIdForFiltering();
|
||||||
@@ -326,7 +333,7 @@ public class CashReportApiResource {
|
|||||||
@GET
|
@GET
|
||||||
@Path("cashier-position/me")
|
@Path("cashier-position/me")
|
||||||
public String cashierPositionMe(@QueryParam("asOfDate") String asOfDate) {
|
public String cashierPositionMe(@QueryParam("asOfDate") String asOfDate) {
|
||||||
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now().toString();
|
final String dateFilter = (asOfDate != null && !asOfDate.isBlank()) ? asOfDate : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
Long appUserId;
|
Long appUserId;
|
||||||
try {
|
try {
|
||||||
appUserId = context.authenticatedUser().getId();
|
appUserId = context.authenticatedUser().getId();
|
||||||
@@ -362,7 +369,7 @@ public class CashReportApiResource {
|
|||||||
@Path("cashier-morning")
|
@Path("cashier-morning")
|
||||||
public String cashierMorning(@QueryParam("cashierId") Long cashierId,
|
public String cashierMorning(@QueryParam("cashierId") Long cashierId,
|
||||||
@QueryParam("date") String date) {
|
@QueryParam("date") String date) {
|
||||||
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
if (cashierId == null) {
|
if (cashierId == null) {
|
||||||
Map<String, Object> empty = new LinkedHashMap<>();
|
Map<String, Object> empty = new LinkedHashMap<>();
|
||||||
empty.put("cashierId", null);
|
empty.put("cashierId", null);
|
||||||
@@ -515,7 +522,9 @@ public class CashReportApiResource {
|
|||||||
cashierName = jdbcTemplate.queryForObject(
|
cashierName = jdbcTemplate.queryForObject(
|
||||||
"SELECT ms.display_name FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id WHERE mc.id = ?",
|
"SELECT ms.display_name FROM m_cashiers mc JOIN m_staff ms ON ms.id = mc.staff_id WHERE mc.id = ?",
|
||||||
String.class, cashierId);
|
String.class, cashierId);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("cashierId", cashierId);
|
result.put("cashierId", cashierId);
|
||||||
@@ -562,7 +571,7 @@ public class CashReportApiResource {
|
|||||||
@QueryParam("offset") Integer offset,
|
@QueryParam("offset") Integer offset,
|
||||||
@QueryParam("limit") Integer limit) {
|
@QueryParam("limit") Integer limit) {
|
||||||
|
|
||||||
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
final String txnDate = (date != null && !date.isBlank()) ? date : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
final int resolvedLimit = (limit != null && limit > 0) ? limit : 20;
|
final int resolvedLimit = (limit != null && limit > 0) ? limit : 20;
|
||||||
final int resolvedOffset = (offset != null && offset >= 0) ? offset : 0;
|
final int resolvedOffset = (offset != null && offset >= 0) ? offset : 0;
|
||||||
|
|
||||||
|
|||||||
+24
-11
@@ -36,6 +36,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashsettlement")
|
@Path("/v1/cashsettlement")
|
||||||
@Component
|
@Component
|
||||||
@@ -70,7 +71,7 @@ public class CashSettlementApiResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final LocalDate settlementDate = json.has("settlementDate") && !json.get("settlementDate").isJsonNull()
|
final LocalDate settlementDate = json.has("settlementDate") && !json.get("settlementDate").isJsonNull()
|
||||||
? LocalDate.parse(json.get("settlementDate").getAsString()) : LocalDate.now();
|
? LocalDate.parse(json.get("settlementDate").getAsString()) : LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
final String fromParty = json.has("fromParty") && !json.get("fromParty").isJsonNull()
|
||||||
? json.get("fromParty").getAsString() : null;
|
? json.get("fromParty").getAsString() : null;
|
||||||
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
final String toParty = json.has("toParty") && !json.get("toParty").isJsonNull()
|
||||||
@@ -116,13 +117,15 @@ public class CashSettlementApiResource {
|
|||||||
header.setToParty(toParty);
|
header.setToParty(toParty);
|
||||||
header.setSettlementAmount(total);
|
header.setSettlementAmount(total);
|
||||||
header.setNote(note);
|
header.setNote(note);
|
||||||
header.setCreatedAt(LocalDateTime.now());
|
header.setCreatedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
header.setIsReversed(false);
|
header.setIsReversed(false);
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
header.setPerformedBy(user.getId());
|
header.setPerformedBy(user.getId());
|
||||||
header.setPerformedByUsername(user.getUsername());
|
header.setPerformedByUsername(user.getUsername());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
// Commit header + details first, then GL dispatch separately to avoid
|
// Commit header + details first, then GL dispatch separately to avoid
|
||||||
// EclipseLink flushing pending detail rows inside Fineract's GL transaction.
|
// EclipseLink flushing pending detail rows inside Fineract's GL transaction.
|
||||||
@@ -142,7 +145,9 @@ public class CashSettlementApiResource {
|
|||||||
Long resolved = jdbcTemplate.queryForObject(
|
Long resolved = jdbcTemplate.queryForObject(
|
||||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, fromCashierId);
|
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, fromCashierId);
|
||||||
if (resolved != null) glTellerId = resolved;
|
if (resolved != null) glTellerId = resolved;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
glDispatchService.settleCashFromCashier(glTellerId, fromCashierId, total,
|
glDispatchService.settleCashFromCashier(glTellerId, fromCashierId, total,
|
||||||
currencyCode, settlementDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
currencyCode, settlementDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
note != null ? note : "");
|
note != null ? note : "");
|
||||||
@@ -174,13 +179,17 @@ public class CashSettlementApiResource {
|
|||||||
try {
|
try {
|
||||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
AppUser user = null;
|
AppUser user = null;
|
||||||
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {}
|
try { user = this.context.authenticatedUser(); } catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
settlement.setIsReversed(true);
|
settlement.setIsReversed(true);
|
||||||
settlement.setReversedAt(LocalDateTime.now());
|
settlement.setReversedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
settlement.setReverseReason(reason);
|
settlement.setReverseReason(reason);
|
||||||
if (user != null) settlement.setReversedBy(user.getId());
|
if (user != null) settlement.setReversedBy(user.getId());
|
||||||
repository.save(settlement);
|
repository.save(settlement);
|
||||||
@@ -192,12 +201,16 @@ public class CashSettlementApiResource {
|
|||||||
Long resolved = jdbcTemplate.queryForObject(
|
Long resolved = jdbcTemplate.queryForObject(
|
||||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, settlement.getFromCashierId());
|
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, settlement.getFromCashierId());
|
||||||
if (resolved != null) glTellerId = resolved;
|
if (resolved != null) glTellerId = resolved;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
glDispatchService.allocateCashToCashier(glTellerId,
|
glDispatchService.allocateCashToCashier(glTellerId,
|
||||||
settlement.getFromCashierId(), settlement.getSettlementAmount(), "INR",
|
settlement.getFromCashierId(), settlement.getSettlementAmount(), "INR",
|
||||||
LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE),
|
LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
"Reversal of cash settlement #" + id + (reason != null ? ": " + reason : ""));
|
"Reversal of cash settlement #" + id + (reason != null ? ": " + reason : ""));
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject result = new JsonObject();
|
JsonObject result = new JsonObject();
|
||||||
@@ -214,7 +227,7 @@ public class CashSettlementApiResource {
|
|||||||
throw new PlatformDataIntegrityException("error.msg.cashsettlement.cashier.required",
|
throw new PlatformDataIntegrityException("error.msg.cashsettlement.cashier.required",
|
||||||
"cashierId query param is required.");
|
"cashierId query param is required.");
|
||||||
}
|
}
|
||||||
final String settlementDate = (date != null && !date.isBlank()) ? date : LocalDate.now().toString();
|
final String settlementDate = (date != null && !date.isBlank()) ? date : LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
Integer count = jdbcTemplate.queryForObject(
|
Integer count = jdbcTemplate.queryForObject(
|
||||||
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
"SELECT COUNT(*) FROM m_cash_settlement WHERE from_cashier_id = ? AND settlement_date = CAST(? AS DATE) AND (is_reversed IS NULL OR is_reversed = false)",
|
||||||
Integer.class, cashierId, settlementDate);
|
Integer.class, cashierId, settlementDate);
|
||||||
|
|||||||
+20
-9
@@ -36,6 +36,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/cashtransfer")
|
@Path("/v1/cashtransfer")
|
||||||
@Component
|
@Component
|
||||||
@@ -105,7 +106,7 @@ public class CashTransferApiResource {
|
|||||||
header.setToCashierId(toCashierId);
|
header.setToCashierId(toCashierId);
|
||||||
header.setTransferAmount(total);
|
header.setTransferAmount(total);
|
||||||
header.setNote(note);
|
header.setNote(note);
|
||||||
header.setCreatedAt(LocalDateTime.now());
|
header.setCreatedAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
@@ -136,8 +137,10 @@ public class CashTransferApiResource {
|
|||||||
Long resolved = jdbcTemplate.queryForObject(
|
Long resolved = jdbcTemplate.queryForObject(
|
||||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, toCashierId);
|
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, toCashierId);
|
||||||
if (resolved != null) glTellerId = resolved;
|
if (resolved != null) glTellerId = resolved;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
final LocalDate txnDate = transferDate != null ? transferDate : LocalDate.now();
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
final LocalDate txnDate = transferDate != null ? transferDate : LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
glDispatchService.allocateCashToCashier(glTellerId, toCashierId, total,
|
glDispatchService.allocateCashToCashier(glTellerId, toCashierId, total,
|
||||||
currencyCode, txnDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
currencyCode, txnDate.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
note != null ? note : "");
|
note != null ? note : "");
|
||||||
@@ -169,15 +172,19 @@ public class CashTransferApiResource {
|
|||||||
try {
|
try {
|
||||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||||
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
reason = json.has("reason") ? json.get("reason").getAsString() : null;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AppUser user = this.context.authenticatedUser();
|
AppUser user = this.context.authenticatedUser();
|
||||||
transfer.setReversedBy(user.getId());
|
transfer.setReversedBy(user.getId());
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
transfer.setIsReversed(true);
|
transfer.setIsReversed(true);
|
||||||
transfer.setReversedAt(java.time.LocalDateTime.now());
|
transfer.setReversedAt(java.time.LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||||
transfer.setReverseReason(reason);
|
transfer.setReverseReason(reason);
|
||||||
repository.save(transfer);
|
repository.save(transfer);
|
||||||
|
|
||||||
@@ -188,12 +195,16 @@ public class CashTransferApiResource {
|
|||||||
Long resolved = jdbcTemplate.queryForObject(
|
Long resolved = jdbcTemplate.queryForObject(
|
||||||
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, transfer.getToCashierId());
|
"SELECT teller_id FROM m_cashiers WHERE id = ?", Long.class, transfer.getToCashierId());
|
||||||
if (resolved != null) glTellerId = resolved;
|
if (resolved != null) glTellerId = resolved;
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
glDispatchService.settleCashFromCashier(glTellerId,
|
glDispatchService.settleCashFromCashier(glTellerId,
|
||||||
transfer.getToCashierId(), transfer.getTransferAmount(), "INR",
|
transfer.getToCashierId(), transfer.getTransferAmount(), "INR",
|
||||||
java.time.LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE),
|
java.time.LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
"Reversal of cash transfer #" + id + (reason != null ? ": " + reason : ""));
|
"Reversal of cash transfer #" + id + (reason != null ? ": " + reason : ""));
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject result = new JsonObject();
|
JsonObject result = new JsonObject();
|
||||||
|
|||||||
+6
-3
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.denominationmaster.domain.SavingsTxnDenomin
|
|||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records cashier denomination breakdown for GL account transactions (journal entries).
|
* Records cashier denomination breakdown for GL account transactions (journal entries).
|
||||||
@@ -90,7 +91,7 @@ public class CashierGlDenominationApiResource {
|
|||||||
final String txnDateStr = fromJsonHelper.extractStringNamed("txnDate", root);
|
final String txnDateStr = fromJsonHelper.extractStringNamed("txnDate", root);
|
||||||
final LocalDate txnDate = (txnDateStr != null && !txnDateStr.isBlank())
|
final LocalDate txnDate = (txnDateStr != null && !txnDateStr.isBlank())
|
||||||
? LocalDate.parse(txnDateStr)
|
? LocalDate.parse(txnDateStr)
|
||||||
: LocalDate.now();
|
: LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
if (!fromJsonHelper.parameterExists("denominationBreakdown", root)) {
|
if (!fromJsonHelper.parameterExists("denominationBreakdown", root)) {
|
||||||
return "{\"result\":\"skipped\"}";
|
return "{\"result\":\"skipped\"}";
|
||||||
@@ -109,7 +110,9 @@ public class CashierGlDenominationApiResource {
|
|||||||
officerId = user.getId();
|
officerId = user.getId();
|
||||||
officerUsername = user.getUsername();
|
officerUsername = user.getUsername();
|
||||||
}
|
}
|
||||||
} catch (final Exception ignored) {}
|
} catch (final Exception ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
|
|
||||||
final String resolvedTxnType = (txnType != null && !txnType.isBlank())
|
final String resolvedTxnType = (txnType != null && !txnType.isBlank())
|
||||||
? txnType.toUpperCase()
|
? txnType.toUpperCase()
|
||||||
@@ -144,7 +147,7 @@ public class CashierGlDenominationApiResource {
|
|||||||
.count(count)
|
.count(count)
|
||||||
.totalAmount(lineTotal)
|
.totalAmount(lineTotal)
|
||||||
.txnDate(txnDate)
|
.txnDate(txnDate)
|
||||||
.createdAt(LocalDateTime.now())
|
.createdAt(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()))
|
||||||
.txnType(resolvedTxnType)
|
.txnType(resolvedTxnType)
|
||||||
.officerId(officerId)
|
.officerId(officerId)
|
||||||
.officerUsername(officerUsername)
|
.officerUsername(officerUsername)
|
||||||
|
|||||||
+3
-2
@@ -29,6 +29,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cashier Share Collection API — POST /v1/cashier/sharecollection
|
* Cashier Share Collection API — POST /v1/cashier/sharecollection
|
||||||
@@ -131,7 +132,7 @@ public class ShareCollectionApiResource {
|
|||||||
final LocalDate entryDate;
|
final LocalDate entryDate;
|
||||||
try {
|
try {
|
||||||
entryDate = transactionDateStr != null
|
entryDate = transactionDateStr != null
|
||||||
? LocalDate.parse(transactionDateStr) : LocalDate.now();
|
? LocalDate.parse(transactionDateStr) : LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
throw new PlatformDataIntegrityException(
|
throw new PlatformDataIntegrityException(
|
||||||
"error.msg.sharecollection.date.invalid",
|
"error.msg.sharecollection.date.invalid",
|
||||||
@@ -193,7 +194,7 @@ public class ShareCollectionApiResource {
|
|||||||
final String txnId = "SC-" + shareAccountId + "-" + System.currentTimeMillis();
|
final String txnId = "SC-" + shareAccountId + "-" + System.currentTimeMillis();
|
||||||
final long userId = currentUser.getId();
|
final long userId = currentUser.getId();
|
||||||
final String collectorName = currentUser.getUsername();
|
final String collectorName = currentUser.getUsername();
|
||||||
final LocalDateTime now = LocalDateTime.now();
|
final LocalDateTime now = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
// ── Post GL journal entries ────────────────────────────────────────
|
// ── Post GL journal entries ────────────────────────────────────────
|
||||||
// Dr Cash at Vault (SHARES_REFERENCE) = totalAmount
|
// Dr Cash at Vault (SHARES_REFERENCE) = totalAmount
|
||||||
|
|||||||
-1
@@ -41,7 +41,6 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer
|
|||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.cfcfrms.data.ComplaintStatsData;
|
import org.apache.fineract.portfolio.cfcfrms.data.ComplaintStatsData;
|
||||||
import org.apache.fineract.portfolio.cfcfrms.data.ComplaintTicketData;
|
import org.apache.fineract.portfolio.cfcfrms.data.ComplaintTicketData;
|
||||||
import org.apache.fineract.portfolio.cfcfrms.data.TimelineEventData;
|
|
||||||
import org.apache.fineract.portfolio.cfcfrms.service.CfcfrmsComplaintService;
|
import org.apache.fineract.portfolio.cfcfrms.service.CfcfrmsComplaintService;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ import org.springframework.stereotype.Component;
|
|||||||
* POST /v1/simulator/cfcfrms/verify-account – Verify account at bank
|
* POST /v1/simulator/cfcfrms/verify-account – Verify account at bank
|
||||||
* POST /v1/simulator/cfcfrms/verify-transaction – Verify transaction ref
|
* POST /v1/simulator/cfcfrms/verify-transaction – Verify transaction ref
|
||||||
*
|
*
|
||||||
* --- STATUS & REPORTING ---
|
* --- STATUS & REPORTING ---
|
||||||
* GET /v1/simulator/cfcfrms/complaint-status/{ref} – Complaint status from CFCFRMS side
|
* GET /v1/simulator/cfcfrms/complaint-status/{ref} – Complaint status from CFCFRMS side
|
||||||
* GET /v1/simulator/cfcfrms/bank-performance – Bank performance metrics
|
* GET /v1/simulator/cfcfrms/bank-performance – Bank performance metrics
|
||||||
* </pre>
|
* </pre>
|
||||||
|
|||||||
+2
-1
@@ -36,6 +36,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
|
|||||||
import org.springframework.context.event.EventListener;
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeds CFCFRMS complaint tickets, audit logs, and simulator logs on startup.
|
* Seeds CFCFRMS complaint tickets, audit logs, and simulator logs on startup.
|
||||||
@@ -59,7 +60,7 @@ public class CfcfrmsDataSeeder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("Seeding CFCFRMS complaint data...");
|
LOG.info("Seeding CFCFRMS complaint data...");
|
||||||
final OffsetDateTime now = OffsetDateTime.now();
|
final OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
// ─── 3 RECEIVED ──────────────────────────────────────────────
|
// ─── 3 RECEIVED ──────────────────────────────────────────────
|
||||||
seedTicket("TKT-20250318-001", "Rajesh Kumar Sharma", "9876543210",
|
seedTicket("TKT-20250318-001", "Rajesh Kumar Sharma", "9876543210",
|
||||||
|
|||||||
+3
-2
@@ -46,6 +46,7 @@ import org.springframework.data.domain.Page;
|
|||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -206,7 +207,7 @@ public class CfcfrmsComplaintServiceImpl implements CfcfrmsComplaintService {
|
|||||||
final Map<String, Object> result = new HashMap<>();
|
final Map<String, Object> result = new HashMap<>();
|
||||||
result.put("escalatedTo", nextIfsc);
|
result.put("escalatedTo", nextIfsc);
|
||||||
result.put("escalationRef", escalationRef);
|
result.put("escalationRef", escalationRef);
|
||||||
result.put("timestamp", OffsetDateTime.now().toString());
|
result.put("timestamp", OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).toString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +219,7 @@ public class CfcfrmsComplaintServiceImpl implements CfcfrmsComplaintService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String generateTicketId() {
|
private String generateTicketId() {
|
||||||
final String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
final String date = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||||
final int seq = (int) (ticketRepository.count() + 1);
|
final int seq = (int) (ticketRepository.count() + 1);
|
||||||
return String.format("TKT-%s-%03d", date, seq);
|
return String.format("TKT-%s-%03d", date, seq);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -25,12 +25,10 @@ import java.time.LocalDate;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.portfolio.cfcfrms.data.CfcfrmsConstants;
|
import org.apache.fineract.portfolio.cfcfrms.data.CfcfrmsConstants;
|
||||||
@@ -44,6 +42,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -76,7 +75,7 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
final Map<String, Object> response = new LinkedHashMap<>();
|
final Map<String, Object> response = new LinkedHashMap<>();
|
||||||
response.put("cfcfrmsRef", cfcfrmsRef);
|
response.put("cfcfrmsRef", cfcfrmsRef);
|
||||||
response.put("ticketId", ticketData.getTicketId());
|
response.put("ticketId", ticketData.getTicketId());
|
||||||
response.put("pushedToBankAt", OffsetDateTime.now().toString());
|
response.put("pushedToBankAt", OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).toString());
|
||||||
response.put("bankAcknowledged", true);
|
response.put("bankAcknowledged", true);
|
||||||
response.put("message", "Complaint pushed to " + CfcfrmsConstants.BANK_IFSC + " successfully");
|
response.put("message", "Complaint pushed to " + CfcfrmsConstants.BANK_IFSC + " successfully");
|
||||||
return response;
|
return response;
|
||||||
@@ -85,7 +84,7 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
@Override
|
@Override
|
||||||
public Map<String, Object> pushHoldDirective(final JsonObject json) {
|
public Map<String, Object> pushHoldDirective(final JsonObject json) {
|
||||||
final String cfcfrmsRef = getStringOrThrow(json, "cfcfrmsRef");
|
final String cfcfrmsRef = getStringOrThrow(json, "cfcfrmsRef");
|
||||||
final String directiveRef = "DIR-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
final String directiveRef = "DIR-" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
||||||
+ "-" + String.format("%04d", RANDOM.nextInt(10000));
|
+ "-" + String.format("%04d", RANDOM.nextInt(10000));
|
||||||
|
|
||||||
final String directiveType = getStringOrThrow(json, "directiveType");
|
final String directiveType = getStringOrThrow(json, "directiveType");
|
||||||
@@ -112,7 +111,7 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
response.put("directiveRef", directiveRef);
|
response.put("directiveRef", directiveRef);
|
||||||
response.put("status", "DIRECTIVE_SENT");
|
response.put("status", "DIRECTIVE_SENT");
|
||||||
response.put("bankResponse", bankResponse);
|
response.put("bankResponse", bankResponse);
|
||||||
response.put("timestamp", OffsetDateTime.now().toString());
|
response.put("timestamp", OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).toString());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
public Map<String, Object> receiveEscalation(final JsonObject json) {
|
public Map<String, Object> receiveEscalation(final JsonObject json) {
|
||||||
final String cfcfrmsRef = getStringOrThrow(json, "cfcfrmsRef");
|
final String cfcfrmsRef = getStringOrThrow(json, "cfcfrmsRef");
|
||||||
final String ticketId = getStringOrThrow(json, "ticketId");
|
final String ticketId = getStringOrThrow(json, "ticketId");
|
||||||
final String escalationRef = "ESC-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MMdd"))
|
final String escalationRef = "ESC-" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyyy-MMdd"))
|
||||||
+ "-" + String.format("%04d", RANDOM.nextInt(10000));
|
+ "-" + String.format("%04d", RANDOM.nextInt(10000));
|
||||||
|
|
||||||
// Extract nextBank from fundsMovedTo
|
// Extract nextBank from fundsMovedTo
|
||||||
@@ -268,7 +267,7 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
final Map<String, Object> response = new LinkedHashMap<>();
|
final Map<String, Object> response = new LinkedHashMap<>();
|
||||||
response.put("transactionFound", true);
|
response.put("transactionFound", true);
|
||||||
response.put("transactionStatus", "COMPLETED");
|
response.put("transactionStatus", "COMPLETED");
|
||||||
response.put("transactionTimestamp", OffsetDateTime.now().minusHours(2).toString());
|
response.put("transactionTimestamp", OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).minusHours(2).toString());
|
||||||
response.put("senderAccount", json.has("accountNumber") ? json.get("accountNumber").getAsString() : "UNKNOWN");
|
response.put("senderAccount", json.has("accountNumber") ? json.get("accountNumber").getAsString() : "UNKNOWN");
|
||||||
response.put("receiverAccount", json.has("accountNumber") ? json.get("accountNumber").getAsString() : "UNKNOWN");
|
response.put("receiverAccount", json.has("accountNumber") ? json.get("accountNumber").getAsString() : "UNKNOWN");
|
||||||
|
|
||||||
@@ -369,14 +368,14 @@ public class CfcfrmsSimulatorServiceImpl implements CfcfrmsSimulatorService {
|
|||||||
.setScale(1, java.math.RoundingMode.HALF_UP) + "%"
|
.setScale(1, java.math.RoundingMode.HALF_UP) + "%"
|
||||||
: "0.0%";
|
: "0.0%";
|
||||||
response.put("tatComplianceRate", tatCompliance);
|
response.put("tatComplianceRate", tatCompliance);
|
||||||
response.put("lastActivityAt", OffsetDateTime.now().toString());
|
response.put("lastActivityAt", OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()).toString());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Helpers ──
|
// ── Helpers ──
|
||||||
|
|
||||||
private String generateCfcfrmsRef() {
|
private String generateCfcfrmsRef() {
|
||||||
final String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MMdd"));
|
final String date = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyyy-MMdd"));
|
||||||
return "CFCFRMS-" + date + "-" + String.format("%04d", RANDOM.nextInt(10000));
|
return "CFCFRMS-" + date + "-" + String.format("%04d", RANDOM.nextInt(10000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-7
@@ -28,12 +28,14 @@ public interface ChequeStockLotRepository extends JpaRepository<ChequeStockLot,
|
|||||||
List<ChequeStockLot> findBySeriesIdAndOfficeIdAndStatusOrderByIdAsc(Long seriesId, Long officeId, String status);
|
List<ChequeStockLot> findBySeriesIdAndOfficeIdAndStatusOrderByIdAsc(Long seriesId, Long officeId, String status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anti-duplication control: a newly received range must not overlap any existing lot's range within the SAME
|
* Anti-duplication control: a newly received range must not overlap any existing lot's range, REGARDLESS of
|
||||||
* series. Each series is its own stationery batch from the press (different leaf count/paper stock) and is
|
* series. A real bank's leaf-serial numbers are allocated from one bank-wide numbering authority — two
|
||||||
* numbered independently — two different series legitimately reusing the same numeric range is normal in
|
* different cheque designs (series) never legitimately reuse the same numeric range, because whichever
|
||||||
* real-world cheque stock, matching how the press assigns a fresh serial block per print order.
|
* account eventually receives books from both would end up holding two leaves with the identical serial
|
||||||
|
* (a collision NpciInwardPushService's drawer-account-number disambiguation can't resolve when both leaves
|
||||||
|
* belong to the same account). This used to be scoped to "within the same series only", which is exactly
|
||||||
|
* what let that collision happen in the first place.
|
||||||
*/
|
*/
|
||||||
@Query("SELECT COUNT(l) FROM ChequeStockLot l WHERE l.series.id = :seriesId AND l.rangeStart <= :rangeEnd AND l.rangeEnd >= :rangeStart")
|
@Query("SELECT COUNT(l) FROM ChequeStockLot l WHERE l.rangeStart <= :rangeEnd AND l.rangeEnd >= :rangeStart")
|
||||||
long countOverlappingInSeries(@Param("seriesId") long seriesId, @Param("rangeStart") long rangeStart,
|
long countOverlappingAnySeries(@Param("rangeStart") long rangeStart, @Param("rangeEnd") long rangeEnd);
|
||||||
@Param("rangeEnd") long rangeEnd);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -223,8 +223,10 @@ public class ChequeBookReadPlatformServiceImpl implements ChequeBookReadPlatform
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ChequeLeafData> retrieveLeafBySerialNo(final Long leafSerialNo) {
|
public List<ChequeLeafData> retrieveLeafBySerialNo(final Long leafSerialNo) {
|
||||||
// a bare serial number is only unique WITHIN a series (see ChequeStockLotRepository.countOverlappingInSeries) —
|
// new stock lots are now checked for overlap across every series (see
|
||||||
// two different series can legitimately share a number, so this can return more than one match
|
// ChequeStockLotRepository.countOverlappingAnySeries), so a bare serial number stays globally unique going
|
||||||
|
// forward. Older lots received before that check existed can still collide, so this stays defensive and
|
||||||
|
// may return more than one match for pre-existing data.
|
||||||
final String sql = "select " + this.leafMapper.schema() + " where f.leaf_serial_no = ? order by s.series_code asc";
|
final String sql = "select " + this.leafMapper.schema() + " where f.leaf_serial_no = ? order by s.series_code asc";
|
||||||
final List<ChequeLeafData> leaves = this.jdbcTemplate.query(sql, this.leafMapper, leafSerialNo);
|
final List<ChequeLeafData> leaves = this.jdbcTemplate.query(sql, this.leafMapper, leafSerialNo);
|
||||||
if (leaves.isEmpty()) {
|
if (leaves.isEmpty()) {
|
||||||
|
|||||||
+8
-5
@@ -185,13 +185,14 @@ public class ChequeBookWritePlatformServiceImpl implements ChequeBookWritePlatfo
|
|||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult receiveStock(final JsonCommand command) {
|
public CommandProcessingResult receiveStock(final JsonCommand command) {
|
||||||
this.validator.validateForStockLotReceive(command.json());
|
this.validator.validateForStockLotReceive(command.json());
|
||||||
final Long seriesId = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.SERIES_ID.getValue());
|
|
||||||
final Long rangeStart = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_START.getValue());
|
final Long rangeStart = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_START.getValue());
|
||||||
final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue());
|
final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue());
|
||||||
// fail fast for the maker; re-checked authoritatively at approval time in case stock changed meanwhile
|
// fail fast for the maker; re-checked authoritatively at approval time in case stock changed meanwhile
|
||||||
if (this.chequeStockLotRepository.countOverlappingInSeries(seriesId, rangeStart, rangeEnd) > 0) {
|
if (this.chequeStockLotRepository.countOverlappingAnySeries(rangeStart, rangeEnd) > 0) {
|
||||||
throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap",
|
throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap",
|
||||||
"The given leaf-serial range overlaps with an existing stock lot for this series.", rangeStart);
|
"The given leaf-serial range overlaps with an existing stock lot (checked across every series, "
|
||||||
|
+ "not just this one) — pick a different range.",
|
||||||
|
rangeStart);
|
||||||
}
|
}
|
||||||
final Long pendingId = submitPendingRequest(ChequeBookPendingRequestType.RECEIVE_STOCK, command);
|
final Long pendingId = submitPendingRequest(ChequeBookPendingRequestType.RECEIVE_STOCK, command);
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(pendingId).build();
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(pendingId).build();
|
||||||
@@ -208,9 +209,11 @@ public class ChequeBookWritePlatformServiceImpl implements ChequeBookWritePlatfo
|
|||||||
final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue());
|
final Long rangeEnd = command.longValueOfParameterNamed(ChequeBookApiJsonInputParams.RANGE_END.getValue());
|
||||||
final String batchRef = command.stringValueOfParameterNamedAllowingNull(ChequeBookApiJsonInputParams.BATCH_REF.getValue());
|
final String batchRef = command.stringValueOfParameterNamedAllowingNull(ChequeBookApiJsonInputParams.BATCH_REF.getValue());
|
||||||
|
|
||||||
if (this.chequeStockLotRepository.countOverlappingInSeries(seriesId, rangeStart, rangeEnd) > 0) {
|
if (this.chequeStockLotRepository.countOverlappingAnySeries(rangeStart, rangeEnd) > 0) {
|
||||||
throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap",
|
throw new GeneralPlatformDomainRuleException("error.msg.chequestocklot.range.overlap",
|
||||||
"The given leaf-serial range overlaps with an existing stock lot for this series.", rangeStart);
|
"The given leaf-serial range overlaps with an existing stock lot (checked across every series, "
|
||||||
|
+ "not just this one) — pick a different range.",
|
||||||
|
rangeStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ChequeSeries series = this.chequeSeriesRepositoryWrapper.findOneWithNotFoundDetection(seriesId);
|
final ChequeSeries series = this.chequeSeriesRepositoryWrapper.findOneWithNotFoundDetection(seriesId);
|
||||||
|
|||||||
+1
-1
@@ -201,7 +201,7 @@ public class NpciInwardPushService {
|
|||||||
final String presentingBranchCode = getString(instrument, "depositBranchCode");
|
final String presentingBranchCode = getString(instrument, "depositBranchCode");
|
||||||
item.recordInwardMeta(getString(instrument, "serialNumber"), presentingBranchCode, batch.getReturnTatDeadline());
|
item.recordInwardMeta(getString(instrument, "serialNumber"), presentingBranchCode, batch.getReturnTatDeadline());
|
||||||
|
|
||||||
final Boolean instrumentStopPaymentFlag = instrument.has("stopPaymentFlag") && !instrument.get("stopPaymentFlag").isJsonNull()
|
final Boolean instrumentStopPaymentFlag = (instrument.has("stopPaymentFlag") && !instrument.get("stopPaymentFlag").isJsonNull())
|
||||||
? instrument.get("stopPaymentFlag").getAsBoolean()
|
? instrument.get("stopPaymentFlag").getAsBoolean()
|
||||||
: null;
|
: null;
|
||||||
item.recordSourceDetails(getString(instrument, "ctsReferenceNumber"), instrumentStopPaymentFlag,
|
item.recordSourceDetails(getString(instrument, "ctsReferenceNumber"), instrumentStopPaymentFlag,
|
||||||
|
|||||||
+11
-54
@@ -70,6 +70,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Path("/v1/clerk/disbursement-requests")
|
@Path("/v1/clerk/disbursement-requests")
|
||||||
@Component
|
@Component
|
||||||
@@ -383,7 +384,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
final JsonArray creditEntries = gson.fromJson(creditEntriesJson, JsonArray.class);
|
final JsonArray creditEntries = gson.fromJson(creditEntriesJson, JsonArray.class);
|
||||||
final Object ptId = record.get("payment_type_id");
|
final Object ptId = record.get("payment_type_id");
|
||||||
final String description = (String) record.get("description");
|
final String description = (String) record.get("description");
|
||||||
final String txnDateStr = LocalDate.now().toString();
|
final String txnDateStr = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).toString();
|
||||||
|
|
||||||
final Long sourceGlId = record.get("gl_account_id") != null
|
final Long sourceGlId = record.get("gl_account_id") != null
|
||||||
? ((Number) record.get("gl_account_id")).longValue() : null;
|
? ((Number) record.get("gl_account_id")).longValue() : null;
|
||||||
@@ -556,7 +557,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
final String entryType = entry.has("accountType") ? entry.get("accountType").getAsString() : "SAVINGS";
|
final String entryType = entry.has("accountType") ? entry.get("accountType").getAsString() : "SAVINGS";
|
||||||
final String acNo = entry.has("accountNo") ? entry.get("accountNo").getAsString() : "?";
|
final String acNo = entry.has("accountNo") ? entry.get("accountNo").getAsString() : "?";
|
||||||
final Long entryPtId = entry.has("paymentTypeId") && !entry.get("paymentTypeId").isJsonNull()
|
final Long entryPtId = entry.has("paymentTypeId") && !entry.get("paymentTypeId").isJsonNull()
|
||||||
? entry.get("paymentTypeId").getAsLong()
|
? Long.valueOf(entry.get("paymentTypeId").getAsLong())
|
||||||
: (ptId != null ? ((Number) ptId).longValue() : null);
|
: (ptId != null ? ((Number) ptId).longValue() : null);
|
||||||
|
|
||||||
if ("GL".equals(entryType)) {
|
if ("GL".equals(entryType)) {
|
||||||
@@ -810,7 +811,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
? req.get("currencyCode").getAsString() : "INR";
|
? req.get("currencyCode").getAsString() : "INR";
|
||||||
final String txnDateStr = req.has("transactionDate") && !req.get("transactionDate").isJsonNull()
|
final String txnDateStr = req.has("transactionDate") && !req.get("transactionDate").isJsonNull()
|
||||||
? req.get("transactionDate").getAsString()
|
? req.get("transactionDate").getAsString()
|
||||||
: LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
|
: LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ISO_LOCAL_DATE);
|
||||||
final String description = req.has("description") && !req.get("description").isJsonNull()
|
final String description = req.has("description") && !req.get("description").isJsonNull()
|
||||||
? req.get("description").getAsString() : null;
|
? req.get("description").getAsString() : null;
|
||||||
|
|
||||||
@@ -830,7 +831,6 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
|
|
||||||
// Validate all entries: savings/current only (no GL), savingsAccountId present, amount > 0
|
// Validate all entries: savings/current only (no GL), savingsAccountId present, amount > 0
|
||||||
final Set<Long> drAccountIds = new LinkedHashSet<>();
|
final Set<Long> drAccountIds = new LinkedHashSet<>();
|
||||||
final Set<Long> crAccountIds = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
for (final JsonObject e : drEntries) {
|
for (final JsonObject e : drEntries) {
|
||||||
final String acTyp = e.has("accountType") ? e.get("accountType").getAsString() : "SAVINGS";
|
final String acTyp = e.has("accountType") ? e.get("accountType").getAsString() : "SAVINGS";
|
||||||
@@ -858,7 +858,6 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
if (drAccountIds.contains(crId)) throw apiError(400,
|
if (drAccountIds.contains(crId)) throw apiError(400,
|
||||||
"Account " + (e.has("accountNo") ? e.get("accountNo").getAsString() : crId)
|
"Account " + (e.has("accountNo") ? e.get("accountNo").getAsString() : crId)
|
||||||
+ " cannot appear on both DR and CR sides of the same transfer.");
|
+ " cannot appear on both DR and CR sides of the same transfer.");
|
||||||
crAccountIds.add(crId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate balanced entry: total DR == total CR
|
// Validate balanced entry: total DR == total CR
|
||||||
@@ -999,7 +998,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
* under concurrent inserts on the same date.
|
* under concurrent inserts on the same date.
|
||||||
*/
|
*/
|
||||||
private String generateBtVoucherNo(final long logId) {
|
private String generateBtVoucherNo(final long logId) {
|
||||||
return "BT" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"))
|
return "BT" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyMMdd"))
|
||||||
+ String.format("%05d", logId);
|
+ String.format("%05d", logId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1053,7 +1052,9 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
try {
|
try {
|
||||||
final Long txnIdLong = Long.parseLong(txnId);
|
final Long txnIdLong = Long.parseLong(txnId);
|
||||||
undoOps.add(() -> undoAccountTransaction(accountId, txnIdLong, isCurrentAccount));
|
undoOps.add(() -> undoAccountTransaction(accountId, txnIdLong, isCurrentAccount));
|
||||||
} catch (NumberFormatException ignored) {}
|
} catch (NumberFormatException ignored) {
|
||||||
|
// intentionally ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void undoAccountTransaction(final Long accountId, final Long transactionId,
|
private void undoAccountTransaction(final Long accountId, final Long transactionId,
|
||||||
@@ -1072,50 +1073,6 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
commandsSourceWritePlatformService.logCommandSource(cmd);
|
commandsSourceWritePlatformService.logCommandSource(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String executeSavingsDeposit(
|
|
||||||
final Long savingsAccountId, final BigDecimal amount,
|
|
||||||
final Long paymentTypeId, final String txnDateStr, final String note) {
|
|
||||||
|
|
||||||
final String date = formatForFineract(txnDateStr);
|
|
||||||
final JsonObject payload = new JsonObject();
|
|
||||||
payload.addProperty("locale", "en");
|
|
||||||
payload.addProperty("dateFormat", "dd MMMM yyyy");
|
|
||||||
payload.addProperty("transactionDate", date);
|
|
||||||
payload.addProperty("transactionAmount", amount);
|
|
||||||
if (paymentTypeId != null) payload.addProperty("paymentTypeId", paymentTypeId);
|
|
||||||
if (note != null && !note.isBlank()) payload.addProperty("note", note);
|
|
||||||
|
|
||||||
final CommandWrapper cmd = new CommandWrapperBuilder()
|
|
||||||
.savingsAccountDeposit(savingsAccountId)
|
|
||||||
.withJson(payload.toString())
|
|
||||||
.build();
|
|
||||||
fineractRequestContextHolder.setAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID, null);
|
|
||||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(cmd);
|
|
||||||
return result.getResourceId() != null ? result.getResourceId().toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String executeCurrentAccountDeposit(
|
|
||||||
final Long currentAccountId, final BigDecimal amount,
|
|
||||||
final Long paymentTypeId, final String txnDateStr, final String note) {
|
|
||||||
|
|
||||||
final String date = formatForFineract(txnDateStr);
|
|
||||||
final JsonObject payload = new JsonObject();
|
|
||||||
payload.addProperty("locale", "en");
|
|
||||||
payload.addProperty("dateFormat", "dd MMMM yyyy");
|
|
||||||
payload.addProperty("transactionDate", date);
|
|
||||||
payload.addProperty("transactionAmount", amount);
|
|
||||||
if (paymentTypeId != null) payload.addProperty("paymentTypeId", paymentTypeId);
|
|
||||||
if (note != null && !note.isBlank()) payload.addProperty("note", note);
|
|
||||||
|
|
||||||
final CommandWrapper cmd = new CommandWrapperBuilder()
|
|
||||||
.currentAccountDeposit(currentAccountId)
|
|
||||||
.withJson(payload.toString())
|
|
||||||
.build();
|
|
||||||
fineractRequestContextHolder.setAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID, null);
|
|
||||||
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(cmd);
|
|
||||||
return result.getResourceId() != null ? result.getResourceId().toString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCurrentAccountById(final Long accountId) {
|
private boolean isCurrentAccountById(final Long accountId) {
|
||||||
try {
|
try {
|
||||||
final Integer count = jdbcTemplate.queryForObject(
|
final Integer count = jdbcTemplate.queryForObject(
|
||||||
@@ -1131,7 +1088,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||||
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1380,7 +1337,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
? (BigDecimal) accts.get(0).get("account_balance_derived") : BigDecimal.ZERO;
|
? (BigDecimal) accts.get(0).get("account_balance_derived") : BigDecimal.ZERO;
|
||||||
|
|
||||||
final java.sql.Date sqlDate = java.sql.Date.valueOf(txnDate);
|
final java.sql.Date sqlDate = java.sql.Date.valueOf(txnDate);
|
||||||
final OffsetDateTime now = OffsetDateTime.now();
|
final OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String refNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
|
final String refNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
|
||||||
final Long userId = currentUser.getId();
|
final Long userId = currentUser.getId();
|
||||||
|
|
||||||
@@ -1534,7 +1491,7 @@ public class ClerkDisbursementRequestApiResource {
|
|||||||
+ ". Available: " + currentBalance + ", Requested: " + amount);
|
+ ". Available: " + currentBalance + ", Requested: " + amount);
|
||||||
|
|
||||||
final java.sql.Date sqlDate = java.sql.Date.valueOf(txnDate);
|
final java.sql.Date sqlDate = java.sql.Date.valueOf(txnDate);
|
||||||
final OffsetDateTime now = OffsetDateTime.now();
|
final OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String refNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
|
final String refNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
|
||||||
final Long userId = currentUser.getId();
|
final Long userId = currentUser.getId();
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -59,6 +59,9 @@ import org.apache.fineract.useradministration.domain.AppUser;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
|
||||||
@Path("/v1/clerk/withdrawal-requests")
|
@Path("/v1/clerk/withdrawal-requests")
|
||||||
@Component
|
@Component
|
||||||
@@ -437,7 +440,7 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
final BigDecimal amount = new BigDecimal(record.get("amount").toString());
|
||||||
final Object ptId = record.get("payment_type_id");
|
final Object ptId = record.get("payment_type_id");
|
||||||
final Object txnDateObj = record.get("txn_date");
|
final Object txnDateObj = record.get("txn_date");
|
||||||
final String txnDateStr = txnDateObj != null ? txnDateObj.toString().split(" ")[0] : null;
|
final String txnDateStr = txnDateObj != null ? Iterables.get(Splitter.on(' ').split(txnDateObj.toString()), 0) : null;
|
||||||
final String description = (String) record.get("description");
|
final String description = (String) record.get("description");
|
||||||
|
|
||||||
String txnId = null;
|
String txnId = null;
|
||||||
@@ -713,7 +716,7 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
final Long savingsAccountId, final BigDecimal amount,
|
final Long savingsAccountId, final BigDecimal amount,
|
||||||
final Object paymentTypeId, final String txnDateStr, final String note) {
|
final Object paymentTypeId, final String txnDateStr, final String note) {
|
||||||
|
|
||||||
final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
||||||
|
|
||||||
final JsonObject payload = new JsonObject();
|
final JsonObject payload = new JsonObject();
|
||||||
@@ -741,7 +744,7 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
final Long currentAccountId, final BigDecimal amount,
|
final Long currentAccountId, final BigDecimal amount,
|
||||||
final Object paymentTypeId, final String txnDateStr, final String note) {
|
final Object paymentTypeId, final String txnDateStr, final String note) {
|
||||||
|
|
||||||
final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
final String today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today;
|
||||||
|
|
||||||
final JsonObject payload = new JsonObject();
|
final JsonObject payload = new JsonObject();
|
||||||
@@ -780,7 +783,7 @@ public class ClerkWithdrawalRequestApiResource {
|
|||||||
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||||
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return ld.format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
return LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("dd MMMM yyyy"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* 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.client.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
|
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.core.Context;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.client.data.ClientPendingEditData;
|
||||||
|
import org.apache.fineract.portfolio.client.service.ClientEditWritePlatformService;
|
||||||
|
import org.apache.fineract.portfolio.client.service.ClientPendingEditReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/v1/clients")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Client Edit Approval",
|
||||||
|
description = "Maker-checker workflow for editing an existing Client's core profile details. Edits are held in "
|
||||||
|
+ "m_client_pending_edit (a pending row there is itself the lock) and only applied to the real client once a "
|
||||||
|
+ "different user (the checker) approves — see /clients/{clientId}/edit/approve and /reject below.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ClientEditApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final ClientEditWritePlatformService clientEditWritePlatformService;
|
||||||
|
private final ClientPendingEditReadPlatformService clientPendingEditReadPlatformService;
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||||
|
private final DefaultToApiJsonSerializer<CommandProcessingResult> toApiJsonSerializer;
|
||||||
|
private final DefaultToApiJsonSerializer<ClientPendingEditData> pendingEditApiJsonSerializer;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{clientId}/edit")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Submit a details edit for a Client",
|
||||||
|
description = "Stages the proposed changes in m_client_pending_edit instead of applying them immediately — see "
|
||||||
|
+ "/clients/{clientId}/edit/approve and /reject below.")
|
||||||
|
public String submitEdit(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasPermissionTo("SUBMITEDIT_CLIENT");
|
||||||
|
|
||||||
|
final JsonCommand command = JsonCommand.fromJsonElement(clientId, this.fromApiJsonHelper.parse(apiRequestBodyAsJson),
|
||||||
|
this.fromApiJsonHelper);
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.clientEditWritePlatformService.submitEditForApproval(clientId, command);
|
||||||
|
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("edit/pending")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "List Client details edits pending approval",
|
||||||
|
description = "Supports status/clientName/fromDate/toDate filtering (fromDate/toDate as yyyy-MM-dd, inclusive, filtered "
|
||||||
|
+ "on submitted date; clientName is a case-insensitive substring match) plus offset/limit pagination — mirrors "
|
||||||
|
+ "/chequebook/pending-requests.")
|
||||||
|
public Page<ClientPendingEditData> retrieveAllPendingEdits(@QueryParam("status") final String status,
|
||||||
|
@QueryParam("clientName") final String clientName, @QueryParam("fromDate") final String fromDate,
|
||||||
|
@QueryParam("toDate") final String toDate, @QueryParam("offset") @DefaultValue("0") final Integer offset,
|
||||||
|
@QueryParam("limit") @DefaultValue("20") final Integer limit) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final int safeLimit = Math.min(limit, 200);
|
||||||
|
return this.clientPendingEditReadPlatformService.retrieveAllPendingEdits(status, clientName, fromDate, toDate, offset, safeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{clientId}/edit/pending")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Retrieve the pending details edit for a Client, if any")
|
||||||
|
public String retrievePendingEdit(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||||
|
@Context final UriInfo uriInfo) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final ClientPendingEditData pendingEdit = this.clientPendingEditReadPlatformService.retrievePendingEdit(clientId);
|
||||||
|
|
||||||
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
|
return this.pendingEditApiJsonSerializer.serialize(settings, pendingEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{clientId}/edit/approve")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Approve a pending Client details edit",
|
||||||
|
description = "Applies the proposed changes to the client. The maker of the edit cannot also approve it — no exceptions.")
|
||||||
|
public String approvePendingEdit(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId) {
|
||||||
|
|
||||||
|
if (this.context.authenticatedUser().hasNotPermissionForAnyOf("APPROVEEDIT_CLIENT")) {
|
||||||
|
throw new NoAuthorizationException("User has no authority to approve client edits");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.clientEditWritePlatformService.approveEdit(clientId);
|
||||||
|
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{clientId}/edit/reject")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Reject a pending Client details edit",
|
||||||
|
description = "Discards the proposed changes without applying them. The maker of the edit cannot also reject it — no exceptions.")
|
||||||
|
public String rejectPendingEdit(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
if (this.context.authenticatedUser().hasNotPermissionForAnyOf("REJECTEDIT_CLIENT")) {
|
||||||
|
throw new NoAuthorizationException("User has no authority to reject client edits");
|
||||||
|
}
|
||||||
|
|
||||||
|
String rejectReason = null;
|
||||||
|
if (apiRequestBodyAsJson != null && !apiRequestBodyAsJson.isBlank()) {
|
||||||
|
final var parsed = this.fromApiJsonHelper.parse(apiRequestBodyAsJson);
|
||||||
|
if (parsed != null && parsed.isJsonObject() && parsed.getAsJsonObject().has("rejectReason")) {
|
||||||
|
rejectReason = parsed.getAsJsonObject().get("rejectReason").getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final CommandProcessingResult result = this.clientEditWritePlatformService.rejectEdit(clientId, rejectReason);
|
||||||
|
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-2
@@ -39,6 +39,8 @@ import jakarta.ws.rs.core.MediaType;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
||||||
import org.glassfish.jersey.media.multipart.FormDataParam;
|
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.Period;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -158,7 +160,13 @@ public class ClientKycApiResource {
|
|||||||
template.put("clientTypeName", clientTypeName);
|
template.put("clientTypeName", clientTypeName);
|
||||||
template.put("legalFormId", legalFormId);
|
template.put("legalFormId", legalFormId);
|
||||||
|
|
||||||
template.put("requiredDocuments", getRequiredDocuments(clientTypeName, legalFormId));
|
// Minor detection mirrors the frontend's own rule (age < 18) so the document list
|
||||||
|
// served here stays consistent with what the UI computes from the same date of birth.
|
||||||
|
final LocalDate dateOfBirth = client.dateOfBirth();
|
||||||
|
final boolean isMinor = dateOfBirth != null && Period.between(dateOfBirth, LocalDate.now()).getYears() < 18;
|
||||||
|
template.put("isMinor", isMinor);
|
||||||
|
|
||||||
|
template.put("requiredDocuments", getRequiredDocuments(clientTypeName, legalFormId, isMinor));
|
||||||
|
|
||||||
return toApiJsonSerializer.serialize(template);
|
return toApiJsonSerializer.serialize(template);
|
||||||
}
|
}
|
||||||
@@ -166,11 +174,21 @@ public class ClientKycApiResource {
|
|||||||
/**
|
/**
|
||||||
* Returns required KYC documents based on client type (RBI Master Direction on KYC).
|
* Returns required KYC documents based on client type (RBI Master Direction on KYC).
|
||||||
*/
|
*/
|
||||||
private List<Map<String, Object>> getRequiredDocuments(final String clientTypeName, final Integer legalFormId) {
|
private List<Map<String, Object>> getRequiredDocuments(final String clientTypeName, final Integer legalFormId,
|
||||||
|
final boolean isMinor) {
|
||||||
final List<Map<String, Object>> docs = new ArrayList<>();
|
final List<Map<String, Object>> docs = new ArrayList<>();
|
||||||
|
|
||||||
// Individual types (legalFormId=1)
|
// Individual types (legalFormId=1)
|
||||||
if (legalFormId != null && legalFormId == 1) {
|
if (legalFormId != null && legalFormId == 1) {
|
||||||
|
if (isMinor) {
|
||||||
|
// Minors cannot hold PAN, Voter ID, Driving Licence or an NREGA job card — RBI
|
||||||
|
// accepts Aadhaar, Passport, Birth Certificate or School ID Card instead.
|
||||||
|
docs.add(doc("Aadhaar Card", "AADHAAR", true));
|
||||||
|
docs.add(doc("Birth Certificate", "BIRTH_CERTIFICATE", false));
|
||||||
|
docs.add(doc("School ID Card", "SCHOOL_ID_CARD", false));
|
||||||
|
docs.add(doc("Passport", "PASSPORT", false));
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
docs.add(doc("Aadhaar Card", "AADHAAR", true));
|
docs.add(doc("Aadhaar Card", "AADHAAR", true));
|
||||||
docs.add(doc("PAN Card", "PAN", true));
|
docs.add(doc("PAN Card", "PAN", true));
|
||||||
docs.add(doc("Voter ID", "VOTER_ID", false));
|
docs.add(doc("Voter ID", "VOTER_ID", false));
|
||||||
|
|||||||
+13
-5
@@ -44,6 +44,7 @@ import java.time.LocalDate;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.commands.data.AuditData;
|
import org.apache.fineract.commands.data.AuditData;
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
import org.apache.fineract.commands.service.AuditReadPlatformService;
|
import org.apache.fineract.commands.service.AuditReadPlatformService;
|
||||||
@@ -142,7 +143,8 @@ public class ClientsApiResource {
|
|||||||
@Produces({ MediaType.APPLICATION_JSON })
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
@Operation(summary = "List Clients", description = "The list capability of clients can support pagination and sorting.\n\n"
|
@Operation(summary = "List Clients", description = "The list capability of clients can support pagination and sorting.\n\n"
|
||||||
+ "Example Requests:\n" + "\n" + "clients\n" + "\n" + "clients?fields=displayName,officeName,timeline\n" + "\n"
|
+ "Example Requests:\n" + "\n" + "clients\n" + "\n" + "clients?fields=displayName,officeName,timeline\n" + "\n"
|
||||||
+ "clients?offset=10&limit=50\n" + "\n" + "clients?orderBy=displayName&sortOrder=DESC")
|
+ "clients?offset=10&limit=50\n" + "\n" + "clients?orderBy=displayName&sortOrder=DESC\n\n"
|
||||||
|
+ "clients?fromDate=2026-01-01&toDate=2026-01-31 (submittedon_date range, yyyy-MM-dd, inclusive)")
|
||||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.GetClientsResponse.class)))
|
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ClientsApiResourceSwagger.GetClientsResponse.class)))
|
||||||
public String retrieveAll(@Context final UriInfo uriInfo,
|
public String retrieveAll(@Context final UriInfo uriInfo,
|
||||||
@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId,
|
@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId,
|
||||||
@@ -157,10 +159,15 @@ public class ClientsApiResource {
|
|||||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||||
@QueryParam("orphansOnly") @Parameter(description = "orphansOnly") final Boolean orphansOnly,
|
@QueryParam("orphansOnly") @Parameter(description = "orphansOnly") final Boolean orphansOnly,
|
||||||
@QueryParam("legalForm") final Integer legalForm) {
|
@QueryParam("legalForm") final Integer legalForm,
|
||||||
|
@QueryParam("fromDate") @Parameter(description = "fromDate") final String fromDate,
|
||||||
|
@QueryParam("toDate") @Parameter(description = "toDate") final String toDate) {
|
||||||
|
|
||||||
|
final LocalDate fromDateParsed = StringUtils.isNotBlank(fromDate) ? LocalDate.parse(fromDate) : null;
|
||||||
|
final LocalDate toDateParsed = StringUtils.isNotBlank(toDate) ? LocalDate.parse(toDate) : null;
|
||||||
|
|
||||||
return retrieveAll(uriInfo, officeId, externalId, displayName, firstname, lastname, status, legalForm, hierarchy, offset, limit,
|
return retrieveAll(uriInfo, officeId, externalId, displayName, firstname, lastname, status, legalForm, hierarchy, offset, limit,
|
||||||
orderBy, sortOrder, orphansOnly, false);
|
orderBy, sortOrder, orphansOnly, false, fromDateParsed, toDateParsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -437,7 +444,7 @@ public class ClientsApiResource {
|
|||||||
public String retrieveAll(final UriInfo uriInfo, final Long officeId, final String externalId, final String displayName,
|
public String retrieveAll(final UriInfo uriInfo, final Long officeId, final String externalId, final String displayName,
|
||||||
final String firstname, final String lastname, final String status, final Integer legalForm, final String hierarchy,
|
final String firstname, final String lastname, final String status, final Integer legalForm, final String hierarchy,
|
||||||
final Integer offset, final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly,
|
final Integer offset, final Integer limit, final String orderBy, final String sortOrder, final Boolean orphansOnly,
|
||||||
final boolean isSelfUser) {
|
final boolean isSelfUser, final LocalDate fromDate, final LocalDate toDate) {
|
||||||
context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
sqlValidator.validate(orderBy);
|
sqlValidator.validate(orderBy);
|
||||||
sqlValidator.validate(sortOrder);
|
sqlValidator.validate(sortOrder);
|
||||||
@@ -445,7 +452,8 @@ public class ClientsApiResource {
|
|||||||
sqlValidator.validate(hierarchy);
|
sqlValidator.validate(hierarchy);
|
||||||
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).officeId(officeId).externalId(externalId)
|
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).officeId(officeId).externalId(externalId)
|
||||||
.name(displayName).hierarchy(hierarchy).firstname(firstname).lastname(lastname).status(status).orphansOnly(orphansOnly)
|
.name(displayName).hierarchy(hierarchy).firstname(firstname).lastname(lastname).status(status).orphansOnly(orphansOnly)
|
||||||
.isSelfUser(isSelfUser).offset(offset).orderBy(orderBy).sortOrder(sortOrder).legalForm(legalForm).build();
|
.isSelfUser(isSelfUser).offset(offset).orderBy(orderBy).sortOrder(sortOrder).legalForm(legalForm).fromDate(fromDate)
|
||||||
|
.toDate(toDate).build();
|
||||||
final Page<ClientData> clientData = clientReadPlatformService.retrieveAll(searchParameters);
|
final Page<ClientData> clientData = clientReadPlatformService.retrieveAll(searchParameters);
|
||||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
|
return toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS);
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.portfolio.client.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.client.data.KycRiskRuleData;
|
||||||
|
import org.apache.fineract.portfolio.client.service.KycRiskRulesReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/v1/kyc/risk-rules")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "KYC Risk Rules", description = "Retrieve database-driven master rules for client AML/CFT risk rating calculation.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class KycRiskRulesApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final KycRiskRulesReadPlatformService readPlatformService;
|
||||||
|
private final DefaultToApiJsonSerializer<KycRiskRuleData> toApiJsonSerializer;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Retrieve KYC risk rules", description = "Returns list of database-defined risk rules ordered by precedence.")
|
||||||
|
public String retrieveRiskRules() {
|
||||||
|
this.context.authenticatedUser();
|
||||||
|
final List<KycRiskRuleData> rules = this.readPlatformService.retrieveAllRules();
|
||||||
|
return this.toApiJsonSerializer.serialize(rules);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-4
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientKyc;
|
import org.apache.fineract.portfolio.client.domain.ClientKyc;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy / stub implementation of {@link CkycSftpUploadService}.
|
* Dummy / stub implementation of {@link CkycSftpUploadService}.
|
||||||
@@ -60,7 +61,7 @@ public class DummyCkycSftpUploadServiceImpl implements CkycSftpUploadService {
|
|||||||
|
|
||||||
final String ckycNumber = generateCkycNumber(kyc.getClient().getId());
|
final String ckycNumber = generateCkycNumber(kyc.getClient().getId());
|
||||||
final String batchId = generateBatchId();
|
final String batchId = generateBatchId();
|
||||||
final LocalDate today = LocalDate.now();
|
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
return CkycUploadResult.builder()
|
return CkycUploadResult.builder()
|
||||||
.ckycNumber(ckycNumber)
|
.ckycNumber(ckycNumber)
|
||||||
@@ -82,7 +83,7 @@ public class DummyCkycSftpUploadServiceImpl implements CkycSftpUploadService {
|
|||||||
return CkycUploadResult.builder()
|
return CkycUploadResult.builder()
|
||||||
.status("FAILED")
|
.status("FAILED")
|
||||||
.remarks("Uploaded file is not a valid ZIP archive (bad magic bytes).")
|
.remarks("Uploaded file is not a valid ZIP archive (bad magic bytes).")
|
||||||
.uploadedOn(LocalDate.now())
|
.uploadedOn(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ public class DummyCkycSftpUploadServiceImpl implements CkycSftpUploadService {
|
|||||||
return CkycUploadResult.builder()
|
return CkycUploadResult.builder()
|
||||||
.ckycNumber(ckycNumber)
|
.ckycNumber(ckycNumber)
|
||||||
.batchId(batchId)
|
.batchId(batchId)
|
||||||
.uploadedOn(LocalDate.now())
|
.uploadedOn(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()))
|
||||||
.status("SUCCESS")
|
.status("SUCCESS")
|
||||||
.remarks("Dummy CKYC ZIP upload accepted (" + zipBytes.length + " bytes). "
|
.remarks("Dummy CKYC ZIP upload accepted (" + zipBytes.length + " bytes). "
|
||||||
+ "Replace with real CERSAI SFTP integration before go-live.")
|
+ "Replace with real CERSAI SFTP integration before go-live.")
|
||||||
@@ -112,7 +113,7 @@ public class DummyCkycSftpUploadServiceImpl implements CkycSftpUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String generateBatchId() {
|
private String generateBatchId() {
|
||||||
return "DUMMYCKYC-" + BATCH_ID_FORMAT.format(LocalDate.now()) + "-"
|
return "DUMMYCKYC-" + BATCH_ID_FORMAT.format(LocalDate.now(DateUtils.getDateTimeZoneOfTenant())) + "-"
|
||||||
+ String.format("%06d", BATCH_COUNTER.getAndIncrement());
|
+ String.format("%06d", BATCH_COUNTER.getAndIncrement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-37
@@ -32,7 +32,6 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -50,6 +49,7 @@ import org.apache.fineract.portfolio.client.ckyc.domain.CkycBatchRepository;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@@ -61,16 +61,22 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
private static final DateTimeFormatter ZIP_NAME_TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
|
private static final DateTimeFormatter ZIP_NAME_TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names of code values (from {@code KYCAddressProofType}) that unambiguously indicate an
|
* Code (m_code.code_name) under which an identifier's document type must be classified for it
|
||||||
* address proof. Aadhaar / Passport / DL / Voter ID can be both ID and address proof — we
|
* to count as an address proof. Matches the same m_code_value config that
|
||||||
* default them to ID (PI) unless the UI tags them explicitly.
|
* KycProofTypesApiResource (GET /kyc/proof-types) reads to populate the CKYC tab's
|
||||||
|
* "Address Proof Document" card — e.g. Aadhaar Card is seeded as a document type under BOTH
|
||||||
|
* KYCIdProofType and KYCAddressProofType (two separate m_client_identifier rows), so trusting
|
||||||
|
* the code a given identifier was actually tagged under is the only way this can agree with
|
||||||
|
* what the CKYC tab already shows as "available".
|
||||||
*/
|
*/
|
||||||
private static final Set<String> ADDRESS_PROOF_ONLY = Set.of(
|
private static final String ADDRESS_PROOF_CODE_NAME = "KYCAddressProofType";
|
||||||
"UTILITY BILLS", "UTILITY BILL", "ELECTRICITY BILL", "WATER BILL", "GAS BILL", "INTERNET BILL",
|
|
||||||
"BANK ACCOUNT STATEMENT / PASSBOOK", "BANK STATEMENT", "POSTPAID MOBILE BILL",
|
// Top-level ZIP folders — one per artefact type, so every client's file of a given type lands
|
||||||
"REGISTERED RENT AGREEMENT", "RENT AGREEMENT", "PROPERTY TAX RECEIPT",
|
// in the same folder instead of all 4 types being interleaved flat at the ZIP root.
|
||||||
"EMPLOYER LETTER (FOR SALARIED EMPLOYEES)", "EMPLOYER LETTER",
|
private static final String PHOTO_FOLDER = "PHOTO/";
|
||||||
"PENSION PAYMENT ORDERS (FOR RETIREES)", "PENSION PAYMENT ORDER");
|
private static final String SIGNATURE_FOLDER = "SIGNATURE/";
|
||||||
|
private static final String ID_PROOF_FOLDER = "ID_PROOF/";
|
||||||
|
private static final String ADDRESS_PROOF_FOLDER = "ADDRESS_PROOF/";
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
private final DocumentReadPlatformService documentReadPlatformService;
|
private final DocumentReadPlatformService documentReadPlatformService;
|
||||||
@@ -86,11 +92,11 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
throw new IllegalArgumentException("clientIds must not be empty");
|
throw new IllegalArgumentException("clientIds must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
final LocalDate today = LocalDate.now();
|
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final int seq = nextBatchSequenceForToday(today);
|
final int seq = nextBatchSequenceForToday(today);
|
||||||
final String batchId = "BATCH-" + BATCH_ID_FMT.format(today) + "-" + String.format("%03d", seq);
|
final String batchId = "BATCH-" + BATCH_ID_FMT.format(today) + "-" + String.format("%03d", seq);
|
||||||
// ZIP filename: "CERSAI-ZIP-<yyyy-MM-dd-HH-mm-ss>.zip" — hyphen-separated per spec.
|
// ZIP filename: "CERSAI-ZIP-<yyyy-MM-dd-HH-mm-ss>.zip" — hyphen-separated per spec.
|
||||||
final String filename = "CERSAI-ZIP-" + ZIP_NAME_TS_FMT.format(java.time.LocalDateTime.now()) + ".zip";
|
final String filename = "CERSAI-ZIP-" + ZIP_NAME_TS_FMT.format(java.time.LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant())) + ".zip";
|
||||||
|
|
||||||
final List<CkycBatchManifestEntry> manifest = new ArrayList<>();
|
final List<CkycBatchManifestEntry> manifest = new ArrayList<>();
|
||||||
final ByteArrayOutputStream zipBytesOut = new ByteArrayOutputStream();
|
final ByteArrayOutputStream zipBytesOut = new ByteArrayOutputStream();
|
||||||
@@ -111,13 +117,30 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
final byte[] zipBytes = zipBytesOut.toByteArray();
|
final byte[] zipBytes = zipBytesOut.toByteArray();
|
||||||
final String checksum = "sha256:" + sha256Hex(zipBytes);
|
final String checksum = "sha256:" + sha256Hex(zipBytes);
|
||||||
|
|
||||||
// Persist the ZIP as an m_document row (parent_entity_type='ckyc_batch', parent_entity_id=seq)
|
// Persist the batch registry row FIRST (without documentId) so we have its real,
|
||||||
// Persistence happens BEFORE we know m_ckyc_batch.id, so we use the synthetic seq as a
|
// DB-generated id before creating the ZIP's m_document row. The document's
|
||||||
// temporary parent_entity_id. The real FK is via m_ckyc_batch.document_id after save.
|
// parent_entity_id must be the actual m_ckyc_batch.id — using the daily `seq` here
|
||||||
|
// (as a stand-in for an id that doesn't exist yet) was wrong: seq resets every day
|
||||||
|
// while m_ckyc_batch.id is a global auto-increment, so GET
|
||||||
|
// /ckyc_batch/{id}/documents/{documentId}/attachment (which matches on the REAL batch
|
||||||
|
// id) 404'd for any batch whose seq didn't coincidentally equal its DB id.
|
||||||
|
final CkycBatch batch = new CkycBatch();
|
||||||
|
batch.setBatchId(batchId);
|
||||||
|
batch.setFilename(filename);
|
||||||
|
batch.setChecksumSha256(checksum);
|
||||||
|
batch.setRecordCount(includedCount);
|
||||||
|
batch.setSizeBytes((long) zipBytes.length);
|
||||||
|
batch.setCreatedOnDate(today);
|
||||||
|
batch.setUploadedToCersai(false);
|
||||||
|
batch.setManifestJson(new Gson().toJson(manifest));
|
||||||
|
|
||||||
|
final CkycBatch saved = ckycBatchRepository.saveAndFlush(batch);
|
||||||
|
|
||||||
|
// Now persist the ZIP as an m_document row keyed to the batch's real id.
|
||||||
final var docResponse = documentWritePlatformService.createDocument(
|
final var docResponse = documentWritePlatformService.createDocument(
|
||||||
DocumentCreateRequest.builder()
|
DocumentCreateRequest.builder()
|
||||||
.entityType("ckyc_batch")
|
.entityType("ckyc_batch")
|
||||||
.entityId((long) seq)
|
.entityId(saved.getId())
|
||||||
.name(batchId)
|
.name(batchId)
|
||||||
.fileName(filename)
|
.fileName(filename)
|
||||||
.description(StringUtils.defaultIfEmpty(remarks, "CERSAI CKYC batch ZIP"))
|
.description(StringUtils.defaultIfEmpty(remarks, "CERSAI CKYC batch ZIP"))
|
||||||
@@ -126,19 +149,8 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
.stream(new ByteArrayInputStream(zipBytes))
|
.stream(new ByteArrayInputStream(zipBytes))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
// Persist the batch registry row
|
saved.setDocumentId(docResponse.getResourceId() != null ? docResponse.getResourceId().longValue() : null);
|
||||||
final CkycBatch batch = new CkycBatch();
|
ckycBatchRepository.saveAndFlush(saved);
|
||||||
batch.setBatchId(batchId);
|
|
||||||
batch.setFilename(filename);
|
|
||||||
batch.setChecksumSha256(checksum);
|
|
||||||
batch.setRecordCount(includedCount);
|
|
||||||
batch.setSizeBytes((long) zipBytes.length);
|
|
||||||
batch.setDocumentId(docResponse.getResourceId() != null ? docResponse.getResourceId().longValue() : null);
|
|
||||||
batch.setCreatedOnDate(today);
|
|
||||||
batch.setUploadedToCersai(false);
|
|
||||||
batch.setManifestJson(new Gson().toJson(manifest));
|
|
||||||
|
|
||||||
final CkycBatch saved = ckycBatchRepository.saveAndFlush(batch);
|
|
||||||
|
|
||||||
log.info("CKYC batch created: id={}, batchId={}, filename={}, recordCount={}/{}, bytes={}",
|
log.info("CKYC batch created: id={}, batchId={}, filename={}, recordCount={}/{}, bytes={}",
|
||||||
saved.getId(), batchId, filename, includedCount, clientIds.size(), zipBytes.length);
|
saved.getId(), batchId, filename, includedCount, clientIds.size(), zipBytes.length);
|
||||||
@@ -184,32 +196,40 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
entry.setCifNumber(cifNumber);
|
entry.setCifNumber(cifNumber);
|
||||||
entry.setDisplayName(displayName);
|
entry.setDisplayName(displayName);
|
||||||
|
|
||||||
// Photo — packaged as "<CIF>-PI-PHOTO.<ext>" using m_client.image_id → m_image
|
// Photo — packaged as "PHOTO/<CIF>-PI-PHOTO.<ext>" using m_client.image_id → m_image.
|
||||||
|
// Every artefact type gets its own top-level ZIP folder, so a batch of N clients produces
|
||||||
|
// one PHOTO/ folder containing all N client photos (and likewise for the other 3 types),
|
||||||
|
// rather than every client's files interleaved flat at the ZIP root.
|
||||||
final Map<String, Object> photo = new LinkedHashMap<>();
|
final Map<String, Object> photo = new LinkedHashMap<>();
|
||||||
photo.put("present", imageId != null);
|
photo.put("present", imageId != null);
|
||||||
entry.setPhoto(photo);
|
entry.setPhoto(photo);
|
||||||
if (imageId != null && cifNumber != null) {
|
if (imageId != null && cifNumber != null) {
|
||||||
packageClientImage(zip, clientId, cifNumber + "-PI-PHOTO", photo);
|
packageClientImage(zip, clientId, PHOTO_FOLDER + cifNumber + "-PI-PHOTO", photo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature — packaged as "<CIF>-PI-SIGNATURE.<ext>"
|
// Signature — packaged as "SIGNATURE/<CIF>-PI-SIGNATURE.<ext>"
|
||||||
final Long signatureDocId = findSignatureDocId(clientId);
|
final Long signatureDocId = findSignatureDocId(clientId);
|
||||||
final Map<String, Object> signature = new LinkedHashMap<>();
|
final Map<String, Object> signature = new LinkedHashMap<>();
|
||||||
signature.put("present", signatureDocId != null);
|
signature.put("present", signatureDocId != null);
|
||||||
entry.setSignature(signature);
|
entry.setSignature(signature);
|
||||||
if (signatureDocId != null && cifNumber != null) {
|
if (signatureDocId != null && cifNumber != null) {
|
||||||
packageFile(zip, "clients", clientId, signatureDocId,
|
packageFile(zip, "clients", clientId, signatureDocId,
|
||||||
cifNumber + "-PI-SIGNATURE", signature);
|
SIGNATURE_FOLDER + cifNumber + "-PI-SIGNATURE", signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID proof + Address proof: iterate m_client_identifier, classify by code value name.
|
// ID proof + Address proof: iterate m_client_identifier, classify by the m_code the
|
||||||
|
// identifier's document type actually belongs to (KYCIdProofType vs KYCAddressProofType) —
|
||||||
|
// the same source of truth KycProofTypesApiResource uses for the CKYC tab, so a document
|
||||||
|
// the client sees as "Address Proof: Available" is never silently dropped here.
|
||||||
// For each identifier, find the attached m_document (one-to-one, latest wins).
|
// For each identifier, find the attached m_document (one-to-one, latest wins).
|
||||||
// NOTE: the `active` column in m_client_identifier stores the status code (200 = active)
|
// NOTE: the `active` column in m_client_identifier stores the status code (200 = active)
|
||||||
// not a 1/0 boolean — so we filter on `status = 200` (ClientIdentifierStatus.ACTIVE).
|
// not a 1/0 boolean — so we filter on `status = 200` (ClientIdentifierStatus.ACTIVE).
|
||||||
final List<Map<String, Object>> identifiers = jdbcTemplate.queryForList(
|
final List<Map<String, Object>> identifiers = jdbcTemplate.queryForList(
|
||||||
"select ci.id as identifierId, ci.document_key as docKey, cv.code_value as codeValue "
|
"select ci.id as identifierId, ci.document_key as docKey, cv.code_value as codeValue, "
|
||||||
|
+ " c.code_name as codeName "
|
||||||
+ " from m_client_identifier ci "
|
+ " from m_client_identifier ci "
|
||||||
+ " join m_code_value cv on cv.id = ci.document_type_id "
|
+ " join m_code_value cv on cv.id = ci.document_type_id "
|
||||||
|
+ " join m_code c on c.id = cv.code_id "
|
||||||
+ " where ci.client_id = ? and (ci.status = 200 or ci.status is null)",
|
+ " where ci.client_id = ? and (ci.status = 200 or ci.status is null)",
|
||||||
clientId);
|
clientId);
|
||||||
|
|
||||||
@@ -221,8 +241,9 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
for (final Map<String, Object> idRow : identifiers) {
|
for (final Map<String, Object> idRow : identifiers) {
|
||||||
final Long identifierId = ((Number) idRow.get("identifierId")).longValue();
|
final Long identifierId = ((Number) idRow.get("identifierId")).longValue();
|
||||||
final String codeValue = (String) idRow.get("codeValue");
|
final String codeValue = (String) idRow.get("codeValue");
|
||||||
|
final String codeName = (String) idRow.get("codeName");
|
||||||
final String docKey = (String) idRow.get("docKey");
|
final String docKey = (String) idRow.get("docKey");
|
||||||
final boolean isAddress = ADDRESS_PROOF_ONLY.contains(codeValue == null ? "" : codeValue.toUpperCase(Locale.ROOT));
|
final boolean isAddress = ADDRESS_PROOF_CODE_NAME.equalsIgnoreCase(codeName);
|
||||||
final String role = isAddress ? "PA" : "PI";
|
final String role = isAddress ? "PA" : "PI";
|
||||||
|
|
||||||
final Long attachedDocId = findLatestDocForIdentifier(identifierId);
|
final Long attachedDocId = findLatestDocForIdentifier(identifierId);
|
||||||
@@ -242,8 +263,9 @@ public class CkycBatchBuilderServiceImpl implements CkycBatchBuilderService {
|
|||||||
bucket.put("key", docKey);
|
bucket.put("key", docKey);
|
||||||
|
|
||||||
if (cifNumber != null) {
|
if (cifNumber != null) {
|
||||||
|
final String folder = isAddress ? ADDRESS_PROOF_FOLDER : ID_PROOF_FOLDER;
|
||||||
packageFile(zip, "client_identifiers", identifierId, attachedDocId,
|
packageFile(zip, "client_identifiers", identifierId, attachedDocId,
|
||||||
cifNumber + "-" + role + "-" + shortCode, bucket);
|
folder + cifNumber + "-" + role + "-" + shortCode, bucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-7
@@ -26,7 +26,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -36,6 +35,7 @@ import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecific
|
|||||||
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@@ -219,6 +219,7 @@ public class CkycReportsReadServiceImpl implements CkycReportsReadService {
|
|||||||
// ===== Customer List =====
|
// ===== Customer List =====
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("StringSplitter")
|
||||||
public Map<String, Object> listCustomers(String name, String branch, String city, String gender, String state, String dateFrom,
|
public Map<String, Object> listCustomers(String name, String branch, String city, String gender, String state, String dateFrom,
|
||||||
String dateTo, Integer page, Integer pageSize, String sort) {
|
String dateTo, Integer page, Integer pageSize, String sort) {
|
||||||
|
|
||||||
@@ -375,13 +376,13 @@ public class CkycReportsReadServiceImpl implements CkycReportsReadService {
|
|||||||
switch (format != null ? format.toLowerCase() : "csv") {
|
switch (format != null ? format.toLowerCase() : "csv") {
|
||||||
case "txt":
|
case "txt":
|
||||||
contentType = "text/plain";
|
contentType = "text/plain";
|
||||||
filename = "ckyc_export_" + LocalDate.now() + ".txt";
|
filename = "ckyc_export_" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()) + ".txt";
|
||||||
content = buildPipeDelimitedExport(rows);
|
content = buildPipeDelimitedExport(rows);
|
||||||
break;
|
break;
|
||||||
case "csv":
|
case "csv":
|
||||||
default:
|
default:
|
||||||
contentType = "text/csv";
|
contentType = "text/csv";
|
||||||
filename = "ckyc_export_" + LocalDate.now() + ".csv";
|
filename = "ckyc_export_" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()) + ".csv";
|
||||||
content = buildCsvExport(rows);
|
content = buildCsvExport(rows);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -431,7 +432,7 @@ public class CkycReportsReadServiceImpl implements CkycReportsReadService {
|
|||||||
errors.add("Parse error: " + e.getMessage());
|
errors.add("Parse error: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
String fileId = "UPLOAD-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + System.currentTimeMillis();
|
String fileId = "UPLOAD-" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()).format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + System.currentTimeMillis();
|
||||||
result.put("fileId", fileId);
|
result.put("fileId", fileId);
|
||||||
result.put("totalRecords", totalRecords);
|
result.put("totalRecords", totalRecords);
|
||||||
result.put("customers", customers);
|
result.put("customers", customers);
|
||||||
@@ -453,9 +454,9 @@ public class CkycReportsReadServiceImpl implements CkycReportsReadService {
|
|||||||
@Override
|
@Override
|
||||||
public Response generateCkycFile(Map<String, Object> request) {
|
public Response generateCkycFile(Map<String, Object> request) {
|
||||||
// Placeholder — delegates to CkycBatchBuilderService for actual ZIP generation
|
// Placeholder — delegates to CkycBatchBuilderService for actual ZIP generation
|
||||||
String content = "10|BATCH|" + LocalDate.now() + "|0\n";
|
String content = "10|BATCH|" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()) + "|0\n";
|
||||||
return Response.ok(content.getBytes(StandardCharsets.UTF_8), "text/plain")
|
return Response.ok(content.getBytes(StandardCharsets.UTF_8), "text/plain")
|
||||||
.header("Content-Disposition", "attachment; filename=\"ckyc_submission_" + LocalDate.now() + ".txt\"")
|
.header("Content-Disposition", "attachment; filename=\"ckyc_submission_" + LocalDate.now(DateUtils.getDateTimeZoneOfTenant()) + ".txt\"")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +573,7 @@ public class CkycReportsReadServiceImpl implements CkycReportsReadService {
|
|||||||
|
|
||||||
private String buildPipeDelimitedExport(List<Map<String, Object>> rows) {
|
private String buildPipeDelimitedExport(List<Map<String, Object>> rows) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("10|CKYC_EXPORT|").append(LocalDate.now()).append("|").append(rows.size()).append("\n");
|
sb.append("10|CKYC_EXPORT|").append(LocalDate.now(DateUtils.getDateTimeZoneOfTenant())).append("|").append(rows.size()).append("\n");
|
||||||
for (Map<String, Object> row : rows) {
|
for (Map<String, Object> row : rows) {
|
||||||
sb.append("20|");
|
sb.append("20|");
|
||||||
sb.append(val(row.get("cif"))).append("|");
|
sb.append(val(row.get("cif"))).append("|");
|
||||||
|
|||||||
+3
-2
@@ -47,6 +47,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy / stub implementation of {@link CkycBatchSftpUploadService}.
|
* Dummy / stub implementation of {@link CkycBatchSftpUploadService}.
|
||||||
@@ -149,10 +150,10 @@ public class DummyCkycBatchSftpUploadServiceImpl implements CkycBatchSftpUploadS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Build the ACK reference and timestamp
|
// 3) Build the ACK reference and timestamp
|
||||||
final LocalDateTime now = LocalDateTime.now();
|
final LocalDateTime now = LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final String ackReference = "CERSAI-ACK-" + ACK_TS_FMT.format(now) + "-"
|
final String ackReference = "CERSAI-ACK-" + ACK_TS_FMT.format(now) + "-"
|
||||||
+ String.format("%04d", ACK_COUNTER.getAndIncrement());
|
+ String.format("%04d", ACK_COUNTER.getAndIncrement());
|
||||||
final LocalDate today = LocalDate.now();
|
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
|
|
||||||
// 4) Parse manifest → generate a CKYC number per included client → persist on m_client_kyc
|
// 4) Parse manifest → generate a CKYC number per included client → persist on m_client_kyc
|
||||||
final List<Map<String, Object>> perClient = new ArrayList<>();
|
final List<Map<String, Object>> perClient = new ArrayList<>();
|
||||||
|
|||||||
+19
-7
@@ -71,6 +71,15 @@ public class ClientKycCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateForInitiate() {
|
public void validateForInitiate() {
|
||||||
|
validateForInitiate(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param skipIdentityDocumentCheck true for minors, who identify via the Birth Certificate /
|
||||||
|
* School ID document-type picker (a separate upload path)
|
||||||
|
* rather than PAN/Aadhaar/GSTIN.
|
||||||
|
*/
|
||||||
|
public void validateForInitiate(final boolean skipIdentityDocumentCheck) {
|
||||||
final List<ApiParameterError> errors = new ArrayList<>();
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(ClientKycApiConstants.RESOURCE_NAME);
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(ClientKycApiConstants.RESOURCE_NAME);
|
||||||
|
|
||||||
@@ -101,13 +110,16 @@ public class ClientKycCommand {
|
|||||||
"GSTIN must be a valid 15-character GST Identification Number");
|
"GSTIN must be a valid 15-character GST Identification Number");
|
||||||
}
|
}
|
||||||
|
|
||||||
// At least one ID document must be provided
|
// At least one ID document must be provided — except for minors, who prove identity via
|
||||||
final boolean hasPan = panNumber != null && !panNumber.isBlank();
|
// the Birth Certificate / School ID document-type picker instead.
|
||||||
final boolean hasAadhaar = aadhaarNumberMasked != null && !aadhaarNumberMasked.isBlank();
|
if (!skipIdentityDocumentCheck) {
|
||||||
final boolean hasGstin = gstinNumber != null && !gstinNumber.isBlank();
|
final boolean hasPan = panNumber != null && !panNumber.isBlank();
|
||||||
if (!hasPan && !hasAadhaar && !hasGstin) {
|
final boolean hasAadhaar = aadhaarNumberMasked != null && !aadhaarNumberMasked.isBlank();
|
||||||
v.reset().parameter(ClientKycApiConstants.panNumberParam).value(panNumber)
|
final boolean hasGstin = gstinNumber != null && !gstinNumber.isBlank();
|
||||||
.notBlank();
|
if (!hasPan && !hasAadhaar && !hasGstin) {
|
||||||
|
v.reset().parameter(ClientKycApiConstants.panNumberParam).value(panNumber)
|
||||||
|
.notBlank();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(errors);
|
throwExceptionIfValidationWarningsExist(errors);
|
||||||
|
|||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* 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.client.contactverification.api;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.PathParam;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.Context;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.client.contactverification.service.ClientContactOtpService;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Staff-facing endpoints used to gate a client's mobileNo / emailAddress change on
|
||||||
|
* {@code PUT /v1/clients/{clientId}} behind dual OTP verification (old destination AND new
|
||||||
|
* destination must each be OTP-verified before the update call will accept the change — see
|
||||||
|
* {@code ClientDataValidator} and {@code ClientWritePlatformServiceJpaRepositoryImpl.updateClient()}).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* OTP is entered by the staff member on the customer's behalf (no self-service customer portal
|
||||||
|
* exists in this deployment), which is why this resource requires a normal, fully-authenticated
|
||||||
|
* staff session rather than being public like the pre-login {@code /v1/login/otp/*} endpoints.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Deliberately does NOT contain the literal path segment {@code /self/} anywhere — that substring
|
||||||
|
* causes {@code TenantAwareBasicAuthenticationFilter} to reject any non-self-service (staff) caller
|
||||||
|
* with a 401, which triggers a browser Basic-Auth popup. See the class-level {@code @Path} below.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* POST /v1/clients/{clientId}/contact-verification/otp body: {"channel":"MOBILE"|"EMAIL","destination":"..."}
|
||||||
|
* POST /v1/clients/{clientId}/contact-verification/otp/verify body: {"otpId":123,"otp":"123456"}
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Path("/v1/clients/{clientId}/contact-verification/otp")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Client Contact Verification",
|
||||||
|
description = "Dual-OTP verification of a client's old/new mobile number or email address, "
|
||||||
|
+ "required before PUT /v1/clients/{clientId} will accept a mobileNo/emailAddress change.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ClientContactVerificationApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
|
private final ClientContactOtpService clientContactOtpService;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Generate an OTP against a client's mobile number or email address",
|
||||||
|
description = "channel=MOBILE sends via LincPay to `destination`; channel=EMAIL sends a locally-generated "
|
||||||
|
+ "6-digit code to `destination` via SMTP. Call this once for the client's *current* (old) "
|
||||||
|
+ "destination and once for the *incoming* (new) destination before attempting the actual "
|
||||||
|
+ "PUT /v1/clients/{clientId} update — both otpId's are required as mobileOldOtpId/mobileNewOtpId "
|
||||||
|
+ "(or emailOldOtpId/emailNewOtpId) on that call.")
|
||||||
|
public OtpGenerateResponse generate(@PathParam("clientId") final Long clientId, final String apiRequestBodyAsJson,
|
||||||
|
@Context final HttpServletRequest httpRequest) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasUpdatePermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||||
|
|
||||||
|
final JsonObject payload = parse(apiRequestBodyAsJson);
|
||||||
|
final String channel = stringOrNull(payload, "channel");
|
||||||
|
final String destination = stringOrNull(payload, "destination");
|
||||||
|
|
||||||
|
final String ipAddress = clientIp(httpRequest);
|
||||||
|
final String userAgent = httpRequest != null ? httpRequest.getHeader("User-Agent") : null;
|
||||||
|
|
||||||
|
if ("EMAIL".equalsIgnoreCase(channel)) {
|
||||||
|
return clientContactOtpService.generateEmailOtp(destination, ipAddress, userAgent);
|
||||||
|
}
|
||||||
|
if ("MOBILE".equalsIgnoreCase(channel)) {
|
||||||
|
return clientContactOtpService.generateMobileOtp(destination, ipAddress, userAgent);
|
||||||
|
}
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.channel.invalid",
|
||||||
|
"channel must be one of MOBILE, EMAIL", channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("verify")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Verify a previously generated client-contact-change OTP")
|
||||||
|
public OtpVerifyResponse verify(@PathParam("clientId") final Long clientId, final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasUpdatePermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
||||||
|
|
||||||
|
final JsonObject payload = parse(apiRequestBodyAsJson);
|
||||||
|
final Long otpId = longOrNull(payload, "otpId");
|
||||||
|
final String otp = stringOrNull(payload, "otp");
|
||||||
|
return clientContactOtpService.verify(otpId, otp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private JsonObject parse(final String body) {
|
||||||
|
if (body == null || body.isBlank()) {
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JsonParser.parseString(body).getAsJsonObject();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringOrNull(final JsonObject obj, final String key) {
|
||||||
|
return obj.has(key) && obj.get(key).isJsonPrimitive() ? obj.get(key).getAsString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longOrNull(final JsonObject obj, final String key) {
|
||||||
|
try {
|
||||||
|
return obj.has(key) && obj.get(key).isJsonPrimitive() ? obj.get(key).getAsLong() : null;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clientIp(final HttpServletRequest req) {
|
||||||
|
if (req == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String fwd = req.getHeader("X-Forwarded-For");
|
||||||
|
if (fwd != null && !fwd.isBlank()) {
|
||||||
|
return Iterables.get(Splitter.on(',').split(fwd), 0).trim();
|
||||||
|
}
|
||||||
|
return req.getRemoteAddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* 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.client.contactverification.api;
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.Context;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.client.contactverification.service.ClientContactOtpService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sibling of {@link ClientContactVerificationApiResource}, for the CLIENT CREATION flow — a
|
||||||
|
* brand-new client has no id yet, so the {@code {clientId}} path segment used by the edit-time
|
||||||
|
* resource (which looks the client up via {@code findOneWithNotFoundDetection}) can't be reused
|
||||||
|
* as-is here. Same {@code ClientContactOtpService} underneath, same request/response shapes —
|
||||||
|
* only the path (no {@code clientId} segment, so no client-existence check) and the permission
|
||||||
|
* (CREATE instead of UPDATE) differ.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Segment count disambiguates this from {@code ClientContactVerificationApiResource}'s path at the
|
||||||
|
* JAX-RS routing level (4 segments here vs. 5 with {@code {clientId}}), so both resources coexist
|
||||||
|
* without ambiguity.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Deliberately does NOT contain the literal path segment {@code /self/} anywhere — see
|
||||||
|
* {@link ClientContactVerificationApiResource}'s class Javadoc for why that substring is unsafe in
|
||||||
|
* this codebase.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* POST /v1/clients/contact-verification/otp body: {"channel":"MOBILE"|"EMAIL","destination":"..."}
|
||||||
|
* POST /v1/clients/contact-verification/otp/verify body: {"otpId":123,"otp":"123456"}
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Path("/v1/clients/contact-verification/otp")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Client Creation Contact Verification",
|
||||||
|
description = "OTP verification of a brand-new client's mobile number / email address at creation time, "
|
||||||
|
+ "required before POST /v1/clients will accept the value — guarantees every mobileNo/emailAddress "
|
||||||
|
+ "ever stored was reachable at the moment it was recorded.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ClientCreationContactVerificationApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final ClientContactOtpService clientContactOtpService;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Generate an OTP against a not-yet-created client's mobile number or email address",
|
||||||
|
description = "channel=MOBILE sends via LincPay to `destination`; channel=EMAIL sends a locally-generated "
|
||||||
|
+ "6-digit code to `destination` via SMTP. The returned otpId is required as mobileNewOtpId / "
|
||||||
|
+ "emailNewOtpId on the subsequent POST /v1/clients call.")
|
||||||
|
public OtpGenerateResponse generate(final String apiRequestBodyAsJson, @Context final HttpServletRequest httpRequest) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasCreatePermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final JsonObject payload = parse(apiRequestBodyAsJson);
|
||||||
|
final String channel = stringOrNull(payload, "channel");
|
||||||
|
final String destination = stringOrNull(payload, "destination");
|
||||||
|
|
||||||
|
final String ipAddress = clientIp(httpRequest);
|
||||||
|
final String userAgent = httpRequest != null ? httpRequest.getHeader("User-Agent") : null;
|
||||||
|
|
||||||
|
if ("EMAIL".equalsIgnoreCase(channel)) {
|
||||||
|
return clientContactOtpService.generateEmailOtp(destination, ipAddress, userAgent);
|
||||||
|
}
|
||||||
|
if ("MOBILE".equalsIgnoreCase(channel)) {
|
||||||
|
return clientContactOtpService.generateMobileOtp(destination, ipAddress, userAgent);
|
||||||
|
}
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.channel.invalid",
|
||||||
|
"channel must be one of MOBILE, EMAIL", channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("verify")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Verify a previously generated client-creation contact OTP")
|
||||||
|
public OtpVerifyResponse verify(final String apiRequestBodyAsJson) {
|
||||||
|
|
||||||
|
this.context.authenticatedUser().validateHasCreatePermission(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final JsonObject payload = parse(apiRequestBodyAsJson);
|
||||||
|
final Long otpId = longOrNull(payload, "otpId");
|
||||||
|
final String otp = stringOrNull(payload, "otp");
|
||||||
|
return clientContactOtpService.verify(otpId, otp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers (mirrors ClientContactVerificationApiResource) ────────────────
|
||||||
|
|
||||||
|
private JsonObject parse(final String body) {
|
||||||
|
if (body == null || body.isBlank()) {
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JsonParser.parseString(body).getAsJsonObject();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringOrNull(final JsonObject obj, final String key) {
|
||||||
|
return obj.has(key) && obj.get(key).isJsonPrimitive() ? obj.get(key).getAsString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long longOrNull(final JsonObject obj, final String key) {
|
||||||
|
try {
|
||||||
|
return obj.has(key) && obj.get(key).isJsonPrimitive() ? obj.get(key).getAsLong() : null;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clientIp(final HttpServletRequest req) {
|
||||||
|
if (req == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String fwd = req.getHeader("X-Forwarded-For");
|
||||||
|
if (fwd != null && !fwd.isBlank()) {
|
||||||
|
return Iterables.get(Splitter.on(',').split(fwd), 0).trim();
|
||||||
|
}
|
||||||
|
return req.getRemoteAddr();
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
@@ -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.client.contactverification.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client-facing OTP generate/verify used to gate mobileNo / emailAddress changes on
|
||||||
|
* {@code PUT /v1/clients/{clientId}} (see {@code ClientDataValidator} and
|
||||||
|
* {@code ClientWritePlatformServiceJpaRepositoryImpl.updateClient()}).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This mirrors {@code LoginOtpService} / {@code EmailLoginOtpService} (same LincPay / local-hash
|
||||||
|
* mechanics, same {@code m_otp_log} bookkeeping) but takes a raw destination directly instead of
|
||||||
|
* resolving one from an {@code AppUser} — here the "user" being verified is a client's contact
|
||||||
|
* channel, entered by a staff member on the client's behalf, not a login user.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Every row created by this service uses {@code purpose = "CLIENT_CONTACT_CHANGE"} so it is
|
||||||
|
* unambiguously distinct from the login-2FA rows in the same table.
|
||||||
|
*/
|
||||||
|
public interface ClientContactOtpService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an OTP to {@code mobileNumber} via LincPay (same call shape as
|
||||||
|
* {@code LoginOtpServiceImpl.generate()}). {@code userId}/{@code username} are left null on the
|
||||||
|
* {@code m_otp_log} row since no AppUser is involved.
|
||||||
|
*/
|
||||||
|
OtpGenerateResponse generateMobileOtp(String mobileNumber, String ipAddress, String userAgent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a 6-digit OTP locally, store its SHA-256 hash, and email it to {@code emailAddress}
|
||||||
|
* (same mechanics as {@code EmailLoginOtpServiceImpl.generate()}).
|
||||||
|
*/
|
||||||
|
OtpGenerateResponse generateEmailOtp(String emailAddress, String ipAddress, String userAgent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify an OTP issued by either of the above. Dispatches to the LincPay delegate or the local
|
||||||
|
* hash-compare depending on the referenced row's {@code provider}.
|
||||||
|
*/
|
||||||
|
OtpVerifyResponse verify(Long otpId, String otp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that {@code otpId} references a {@code CLIENT_CONTACT_CHANGE}, {@code VERIFIED}
|
||||||
|
* {@code m_otp_log} row whose stored mobile number equals {@code expectedMobileNumber}. Throws
|
||||||
|
* {@link org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException} on
|
||||||
|
* any mismatch. Used by {@code ClientWritePlatformServiceJpaRepositoryImpl} immediately before
|
||||||
|
* applying a mobileNo change — never trusts the client-supplied JSON, always re-derives the
|
||||||
|
* expected destination from the DB (old) or the incoming command (new).
|
||||||
|
*
|
||||||
|
* @param otpIdParamLabel the request parameter name this otpId came from (e.g. {@code mobileOldOtpId},
|
||||||
|
* {@code mobileNewOtpId}, {@code emailOldOtpId}, {@code emailNewOtpId}) — used
|
||||||
|
* only to make the error message useful.
|
||||||
|
*/
|
||||||
|
void assertVerifiedMobile(Long otpId, String expectedMobileNumber, String otpIdParamLabel);
|
||||||
|
|
||||||
|
/** Email counterpart of {@link #assertVerifiedMobile(Long, String, String)}. */
|
||||||
|
void assertVerifiedEmail(Long otpId, String expectedEmailAddress, String otpIdParamLabel);
|
||||||
|
}
|
||||||
+463
@@ -0,0 +1,463 @@
|
|||||||
|
/**
|
||||||
|
* 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.client.contactverification.service;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.email.service.EmailSenderService;
|
||||||
|
import org.apache.fineract.infrastructure.email.service.EmailTemplateService;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpGenerateResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.data.OtpVerifyResponse;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLog;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.domain.OtpLogRepository;
|
||||||
|
import org.apache.fineract.infrastructure.security.otp.util.OtpDestinationMasker;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import org.springframework.web.client.HttpServerErrorException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link ClientContactOtpService}. Deliberately does NOT touch {@code LoginOtpServiceImpl} /
|
||||||
|
* {@code EmailLoginOtpServiceImpl} — this is a parallel, client-contact-change-specific variant that
|
||||||
|
* happens to talk to the same LincPay endpoints / SMTP path with a raw destination instead of an
|
||||||
|
* AppUser lookup. Small helper methods (JSON post/parse, sha256, six-digit generator) are duplicated
|
||||||
|
* rather than extracted, matching how this codebase already keeps each OTP-issuing service
|
||||||
|
* self-contained (only the genuinely cross-cutting {@link OtpDestinationMasker} was pulled out).
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ClientContactOtpServiceImpl implements ClientContactOtpService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ClientContactOtpServiceImpl.class);
|
||||||
|
|
||||||
|
public static final String PURPOSE_CLIENT_CONTACT_CHANGE = "CLIENT_CONTACT_CHANGE";
|
||||||
|
private static final String PROVIDER_LINCPAY = "LINCPAY";
|
||||||
|
private static final String PROVIDER_EMAIL = "EMAIL";
|
||||||
|
|
||||||
|
private static final String STATUS_PENDING = "PENDING";
|
||||||
|
private static final String STATUS_SENT = "SENT";
|
||||||
|
private static final String STATUS_FAILED = "FAILED";
|
||||||
|
private static final String STATUS_VERIFIED = "VERIFIED";
|
||||||
|
private static final String STATUS_EXPIRED = "EXPIRED";
|
||||||
|
|
||||||
|
private static final DateTimeFormatter SUBJECT_TIMESTAMP = DateTimeFormatter.ofPattern("dd-MM-yy-HH:mm");
|
||||||
|
|
||||||
|
@Value("${fineract.kyc.lincpay.api-key}")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@Value("${fineract.kyc.lincpay.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${fineract.kyc.lincpay.base-url}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Value("${fineract.security.otp.lincpay.generate-path:/mobile/generate-otp}")
|
||||||
|
private String generateOtpPath;
|
||||||
|
|
||||||
|
@Value("${fineract.security.otp.lincpay.verify-path:/verify-mobile-otp}")
|
||||||
|
private String verifyOtpPath;
|
||||||
|
|
||||||
|
@Value("${fineract.security.otp.ttl-seconds:300}")
|
||||||
|
private int ttlSeconds;
|
||||||
|
|
||||||
|
@Value("${fineract.security.otp.max-attempts:5}")
|
||||||
|
private int maxAttempts;
|
||||||
|
|
||||||
|
@Value("${fineract.email.from-name:CBSnexora}")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final OtpLogRepository otpLogRepository;
|
||||||
|
private final EmailSenderService emailSenderService;
|
||||||
|
private final EmailTemplateService emailTemplateService;
|
||||||
|
private final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ClientContactOtpServiceImpl(final OtpLogRepository otpLogRepository, final EmailSenderService emailSenderService,
|
||||||
|
final EmailTemplateService emailTemplateService) {
|
||||||
|
this.restTemplate = new RestTemplate();
|
||||||
|
this.otpLogRepository = otpLogRepository;
|
||||||
|
this.emailSenderService = emailSenderService;
|
||||||
|
this.emailTemplateService = emailTemplateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Generate ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public OtpGenerateResponse generateMobileOtp(final String mobileNumber, final String ipAddress, final String userAgent) {
|
||||||
|
if (StringUtils.isBlank(mobileNumber)) {
|
||||||
|
return OtpGenerateResponse.builder().success(false).message("mobileNumber is required").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final OtpLog log = new OtpLog();
|
||||||
|
log.setPurpose(PURPOSE_CLIENT_CONTACT_CHANGE);
|
||||||
|
log.setProvider(PROVIDER_LINCPAY);
|
||||||
|
log.setMobileNumber(mobileNumber);
|
||||||
|
log.setStatus(STATUS_PENDING);
|
||||||
|
log.setIpAddress(ipAddress);
|
||||||
|
log.setUserAgent(userAgent);
|
||||||
|
log.setExpiresAt(OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(ttlSeconds));
|
||||||
|
|
||||||
|
final JsonObject requestBody = new JsonObject();
|
||||||
|
requestBody.addProperty("apiKey", apiKey);
|
||||||
|
requestBody.addProperty("mobileNumber", mobileNumber);
|
||||||
|
requestBody.addProperty("secretKey", secret);
|
||||||
|
|
||||||
|
final JsonObject redactedRequest = new JsonObject();
|
||||||
|
redactedRequest.addProperty("apiKey", "***REDACTED***");
|
||||||
|
redactedRequest.addProperty("mobileNumber", mobileNumber);
|
||||||
|
redactedRequest.addProperty("secretKey", "***REDACTED***");
|
||||||
|
log.setRequestPayload(redactedRequest.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String url = baseUrl + generateOtpPath;
|
||||||
|
final String rawResponse = post(url, requestBody.toString());
|
||||||
|
log.setResponsePayload(truncate(rawResponse, 65000));
|
||||||
|
|
||||||
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
if (isSuccess(response)) {
|
||||||
|
log.setStatus(STATUS_SENT);
|
||||||
|
log.setOtpReference(extractField(response, "reference_id", "refNumber", "ref_number", "transactionId", "txnId",
|
||||||
|
"requestId"));
|
||||||
|
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
||||||
|
LOG.info("Client contact-change mobile OTP sent. otpId={}, mobile={}", saved.getId(),
|
||||||
|
OtpDestinationMasker.maskMobile(mobileNumber));
|
||||||
|
return OtpGenerateResponse.builder().success(true).otpId(saved.getId()).otpReference(saved.getOtpReference())
|
||||||
|
.maskedMobile(OtpDestinationMasker.maskMobile(mobileNumber)).expiresInSeconds(ttlSeconds)
|
||||||
|
.message("OTP sent").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String errorMsg = extractField(response, "message", "errorMessage", "error", "description");
|
||||||
|
log.setStatus(STATUS_FAILED);
|
||||||
|
log.setErrorMessage(errorMsg);
|
||||||
|
otpLogRepository.saveAndFlush(log);
|
||||||
|
LOG.warn("Client contact-change mobile OTP failed: {}", errorMsg);
|
||||||
|
return OtpGenerateResponse.builder().success(false).message(errorMsg != null ? errorMsg : "OTP send failed").build();
|
||||||
|
|
||||||
|
} catch (final HttpClientErrorException | HttpServerErrorException e) {
|
||||||
|
log.setStatus(STATUS_FAILED);
|
||||||
|
log.setErrorMessage("HTTP " + e.getStatusCode() + ": " + truncate(e.getResponseBodyAsString(), 900));
|
||||||
|
log.setResponsePayload(truncate(e.getResponseBodyAsString(), 65000));
|
||||||
|
otpLogRepository.saveAndFlush(log);
|
||||||
|
LOG.error("Client contact-change mobile OTP HTTP error: {} {}", e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
|
return OtpGenerateResponse.builder().success(false).message("OTP send failed (HTTP " + e.getStatusCode() + ")").build();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.setStatus(STATUS_FAILED);
|
||||||
|
log.setErrorMessage(truncate(e.getMessage(), 900));
|
||||||
|
otpLogRepository.saveAndFlush(log);
|
||||||
|
LOG.error("Client contact-change mobile OTP unexpected error", e);
|
||||||
|
return OtpGenerateResponse.builder().success(false).message("OTP send failed: " + e.getMessage()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public OtpGenerateResponse generateEmailOtp(final String emailAddress, final String ipAddress, final String userAgent) {
|
||||||
|
if (StringUtils.isBlank(emailAddress)) {
|
||||||
|
return OtpGenerateResponse.builder().success(false).message("emailAddress is required").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String otp = generateSixDigitOtp();
|
||||||
|
final int validityMinutes = Math.max(1, ttlSeconds / 60);
|
||||||
|
|
||||||
|
final OtpLog log = new OtpLog();
|
||||||
|
log.setPurpose(PURPOSE_CLIENT_CONTACT_CHANGE);
|
||||||
|
log.setProvider(PROVIDER_EMAIL);
|
||||||
|
log.setEmailAddress(emailAddress);
|
||||||
|
log.setOtpHash(sha256(otp));
|
||||||
|
log.setStatus(STATUS_PENDING);
|
||||||
|
log.setIpAddress(ipAddress);
|
||||||
|
log.setUserAgent(userAgent);
|
||||||
|
log.setExpiresAt(OffsetDateTime.now(ZoneOffset.UTC).plusSeconds(ttlSeconds));
|
||||||
|
|
||||||
|
final String html = emailTemplateService.renderOtp(null, otp, validityMinutes);
|
||||||
|
final String subject = "Your OTP for Contact Verification - " + appName + " (" + LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()).format(SUBJECT_TIMESTAMP)
|
||||||
|
+ ")";
|
||||||
|
|
||||||
|
final boolean sent = emailSenderService.sendHtml(emailAddress, subject, html);
|
||||||
|
if (sent) {
|
||||||
|
log.setStatus(STATUS_SENT);
|
||||||
|
final OtpLog saved = otpLogRepository.saveAndFlush(log);
|
||||||
|
LOG.info("Client contact-change email OTP sent. otpId={}, email={}", saved.getId(),
|
||||||
|
OtpDestinationMasker.maskEmail(emailAddress));
|
||||||
|
return OtpGenerateResponse.builder().success(true).otpId(saved.getId())
|
||||||
|
.maskedMobile(OtpDestinationMasker.maskEmail(emailAddress)).expiresInSeconds(ttlSeconds)
|
||||||
|
.message("OTP sent to the given email address").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.setStatus(STATUS_FAILED);
|
||||||
|
log.setErrorMessage("SMTP send failed");
|
||||||
|
otpLogRepository.saveAndFlush(log);
|
||||||
|
return OtpGenerateResponse.builder().success(false).message("OTP send failed").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Verify ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public OtpVerifyResponse verify(final Long otpId, final String otp) {
|
||||||
|
if (otpId == null || StringUtils.isBlank(otp)) {
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("otpId and otp are required").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
final OtpLog row = otpLogRepository.findFirstByIdAndIsDeletedFalseOrderByIdDesc(otpId).orElse(null);
|
||||||
|
if (row == null) {
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP not found").build();
|
||||||
|
}
|
||||||
|
if (!PURPOSE_CLIENT_CONTACT_CHANGE.equals(row.getPurpose())) {
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP purpose mismatch").build();
|
||||||
|
}
|
||||||
|
if (STATUS_VERIFIED.equals(row.getStatus())) {
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP already used").build();
|
||||||
|
}
|
||||||
|
if (row.getExpiresAt() != null && OffsetDateTime.now(ZoneOffset.UTC).isAfter(row.getExpiresAt())) {
|
||||||
|
row.setStatus(STATUS_EXPIRED);
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP expired").build();
|
||||||
|
}
|
||||||
|
if (row.getAttempts() != null && row.getAttempts() >= maxAttempts) {
|
||||||
|
row.setStatus(STATUS_EXPIRED);
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("Max attempts exceeded").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
row.setAttempts((row.getAttempts() == null ? 0 : row.getAttempts()) + 1);
|
||||||
|
|
||||||
|
if (PROVIDER_LINCPAY.equals(row.getProvider())) {
|
||||||
|
return verifyViaLincPay(row, otp);
|
||||||
|
}
|
||||||
|
return verifyViaLocalHash(row, otp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OtpVerifyResponse verifyViaLincPay(final OtpLog row, final String otp) {
|
||||||
|
row.setOtpHash(sha256(otp));
|
||||||
|
|
||||||
|
final JsonObject requestBody = new JsonObject();
|
||||||
|
requestBody.addProperty("apiKey", apiKey);
|
||||||
|
requestBody.addProperty("mobileNumber", row.getMobileNumber());
|
||||||
|
requestBody.addProperty("otp", otp);
|
||||||
|
requestBody.addProperty("secretKey", secret);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String url = baseUrl + verifyOtpPath;
|
||||||
|
final String rawResponse = post(url, requestBody.toString());
|
||||||
|
final String existing = row.getResponsePayload() == null ? "" : row.getResponsePayload();
|
||||||
|
row.setResponsePayload(truncate(existing + "\n--verify--\n" + rawResponse, 65000));
|
||||||
|
|
||||||
|
final JsonObject response = parseJson(rawResponse);
|
||||||
|
if (isSuccess(response)) {
|
||||||
|
row.setStatus(STATUS_VERIFIED);
|
||||||
|
row.setVerifiedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
LOG.info("Client contact-change mobile OTP verified. otpId={}, mobile={}", row.getId(),
|
||||||
|
OtpDestinationMasker.maskMobile(row.getMobileNumber()));
|
||||||
|
return OtpVerifyResponse.builder().verified(true).message("OTP verified").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
final String providerMsg = extractField(response, "message", "errorMessage", "error", "description");
|
||||||
|
final int remaining = Math.max(0, maxAttempts - row.getAttempts());
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message(providerMsg != null ? providerMsg : "Invalid OTP")
|
||||||
|
.attemptsRemaining(remaining).build();
|
||||||
|
|
||||||
|
} catch (final HttpClientErrorException | HttpServerErrorException e) {
|
||||||
|
row.setErrorMessage("HTTP " + e.getStatusCode() + ": " + truncate(e.getResponseBodyAsString(), 900));
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
LOG.error("Client contact-change verify-mobile-otp HTTP error: {} {}", e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP verification failed (HTTP " + e.getStatusCode() + ")")
|
||||||
|
.build();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
row.setErrorMessage(truncate(e.getMessage(), 900));
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
LOG.error("Client contact-change verify-mobile-otp unexpected error", e);
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("OTP verification failed: " + e.getMessage()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OtpVerifyResponse verifyViaLocalHash(final OtpLog row, final String otp) {
|
||||||
|
final boolean matched = row.getOtpHash() != null && row.getOtpHash().equals(sha256(otp));
|
||||||
|
if (matched) {
|
||||||
|
row.setStatus(STATUS_VERIFIED);
|
||||||
|
row.setVerifiedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
LOG.info("Client contact-change email OTP verified. otpId={}", row.getId());
|
||||||
|
return OtpVerifyResponse.builder().verified(true).message("OTP verified").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
otpLogRepository.saveAndFlush(row);
|
||||||
|
final int remaining = Math.max(0, maxAttempts - row.getAttempts());
|
||||||
|
return OtpVerifyResponse.builder().verified(false).message("Invalid OTP").attemptsRemaining(remaining).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Assertion helpers used by ClientWritePlatformServiceJpaRepositoryImpl ──
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertVerifiedMobile(final Long otpId, final String expectedMobileNumber, final String otpIdParamLabel) {
|
||||||
|
final OtpLog row = requireVerifiedRow(otpId, otpIdParamLabel);
|
||||||
|
if (!equalsIgnoringWhitespace(row.getMobileNumber(), expectedMobileNumber)) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.destination.mismatch",
|
||||||
|
"The OTP referenced by '" + otpIdParamLabel + "' was not issued for this mobile number.", otpIdParamLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertVerifiedEmail(final Long otpId, final String expectedEmailAddress, final String otpIdParamLabel) {
|
||||||
|
final OtpLog row = requireVerifiedRow(otpId, otpIdParamLabel);
|
||||||
|
if (!equalsIgnoreCaseAndWhitespace(row.getEmailAddress(), expectedEmailAddress)) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.destination.mismatch",
|
||||||
|
"The OTP referenced by '" + otpIdParamLabel + "' was not issued for this email address.", otpIdParamLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OtpLog requireVerifiedRow(final Long otpId, final String otpIdParamLabel) {
|
||||||
|
if (otpId == null) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.required",
|
||||||
|
"'" + otpIdParamLabel + "' is required to change this contact detail.", otpIdParamLabel);
|
||||||
|
}
|
||||||
|
final OtpLog row = otpLogRepository.findFirstByIdAndIsDeletedFalseOrderByIdDesc(otpId).orElse(null);
|
||||||
|
if (row == null) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.not.found",
|
||||||
|
"No OTP record found for '" + otpIdParamLabel + "' = " + otpId + ".", otpIdParamLabel, otpId);
|
||||||
|
}
|
||||||
|
if (!PURPOSE_CLIENT_CONTACT_CHANGE.equals(row.getPurpose())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.purpose.mismatch",
|
||||||
|
"'" + otpIdParamLabel + "' does not reference a contact-change OTP.", otpIdParamLabel);
|
||||||
|
}
|
||||||
|
if (!STATUS_VERIFIED.equals(row.getStatus())) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.client.contact.otp.not.verified",
|
||||||
|
"'" + otpIdParamLabel + "' has not been verified yet. Complete the OTP verification step first.", otpIdParamLabel);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean equalsIgnoringWhitespace(final String a, final String b) {
|
||||||
|
final String na = a == null ? null : a.trim();
|
||||||
|
final String nb = b == null ? null : b.trim();
|
||||||
|
return na != null && na.equals(nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean equalsIgnoreCaseAndWhitespace(final String a, final String b) {
|
||||||
|
final String na = a == null ? null : a.trim().toLowerCase(Locale.ROOT);
|
||||||
|
final String nb = b == null ? null : b.trim().toLowerCase(Locale.ROOT);
|
||||||
|
return na != null && na.equals(nb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers (deliberately duplicated from LoginOtpServiceImpl/EmailLoginOtpServiceImpl —
|
||||||
|
// see class Javadoc) ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private String post(final String url, final String requestBodyJson) {
|
||||||
|
final HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.set("accept", "*/*");
|
||||||
|
final HttpEntity<String> entity = new HttpEntity<>(requestBodyJson, headers);
|
||||||
|
final ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
|
||||||
|
return response.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonObject parseJson(final String raw) {
|
||||||
|
if (raw == null || raw.isBlank()) {
|
||||||
|
return new JsonObject();
|
||||||
|
}
|
||||||
|
final JsonElement parsed = JsonParser.parseString(raw);
|
||||||
|
return parsed.isJsonObject() ? parsed.getAsJsonObject() : new JsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSuccess(final JsonObject response) {
|
||||||
|
for (final String key : new String[] { "status", "statusCode", "responseCode", "code" }) {
|
||||||
|
if (response.has(key)) {
|
||||||
|
final JsonElement v = response.get(key);
|
||||||
|
if (v.isJsonPrimitive()) {
|
||||||
|
final String s = v.getAsString();
|
||||||
|
if ("200".equals(s) || "0".equals(s) || "true".equalsIgnoreCase(s) || "success".equalsIgnoreCase(s)
|
||||||
|
|| "SUCCESS".equalsIgnoreCase(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (response.has("success") && response.get("success").isJsonPrimitive()) {
|
||||||
|
return response.get("success").getAsBoolean();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractField(final JsonObject obj, final String... keys) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final String key : keys) {
|
||||||
|
if (obj.has(key) && obj.get(key).isJsonPrimitive()) {
|
||||||
|
return obj.get(key).getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obj.has("data") && obj.get("data").isJsonObject()) {
|
||||||
|
final JsonObject data = obj.getAsJsonObject("data");
|
||||||
|
for (final String key : keys) {
|
||||||
|
if (data.has(key) && data.get(key).isJsonPrimitive()) {
|
||||||
|
return data.get(key).getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateSixDigitOtp() {
|
||||||
|
final int n = secureRandom.nextInt(1_000_000);
|
||||||
|
return String.format("%06d", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sha256(final String input) {
|
||||||
|
try {
|
||||||
|
final MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
return HexFormat.of().formatHex(md.digest(input.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
} catch (final NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("SHA-256 not available", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String truncate(final String s, final int max) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return s.length() <= max ? s : s.substring(0, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-6
@@ -32,14 +32,16 @@ public class ClientApiCollectionConstants extends ClientApiConstants {
|
|||||||
dateOfBirthParamName, genderIdParamName, salutationIdParamName, clientTypeIdParamName, clientClassificationIdParamName,
|
dateOfBirthParamName, genderIdParamName, salutationIdParamName, clientTypeIdParamName, clientClassificationIdParamName,
|
||||||
occupationCategoryIdParamName, occupationIdParamName, maritalStatusIdParamName,
|
occupationCategoryIdParamName, occupationIdParamName, maritalStatusIdParamName,
|
||||||
educationLevelIdParamName, degreeQualificationIdParamName,
|
educationLevelIdParamName, degreeQualificationIdParamName,
|
||||||
religionIdParamName, socialCategoryIdParamName, monthlyIncomeParamName, annualTurnoverParamName,
|
religionIdParamName, socialCategoryIdParamName, monthlyIncomeParamName, annualIncomeParamName, annualTurnoverParamName,
|
||||||
agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
monthlyTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||||
|
isSeniorCitizenParamName, isPwdParamName, pwdCategoryIdParamName,
|
||||||
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
||||||
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianRelationParamName,
|
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianRelationParamName,
|
||||||
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
||||||
alternateDipVerifiedParamName, alternateDipLookupLogIdParamName, alternateDipTimestampParamName,
|
alternateDipVerifiedParamName, alternateDipLookupLogIdParamName, alternateDipTimestampParamName,
|
||||||
nameVerifiedParamName, screeningLogIdParamName,
|
nameVerifiedParamName, screeningLogIdParamName,
|
||||||
clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, datatables, isStaffParamName, kycParamName));
|
clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, datatables, isStaffParamName, kycParamName,
|
||||||
|
mobileNewOtpIdParamName, emailNewOtpIdParamName));
|
||||||
|
|
||||||
protected static final Set<String> CLIENT_NON_PERSON_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
protected static final Set<String> CLIENT_NON_PERSON_CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||||
Arrays.asList(familyMembers, address, localeParamName, dateFormatParamName, incorpNumberParamName, remarksParamName,
|
Arrays.asList(familyMembers, address, localeParamName, dateFormatParamName, incorpNumberParamName, remarksParamName,
|
||||||
@@ -54,14 +56,16 @@ public class ClientApiCollectionConstants extends ClientApiConstants {
|
|||||||
dateOfBirthParamName, genderIdParamName, salutationIdParamName, clientTypeIdParamName, clientClassificationIdParamName,
|
dateOfBirthParamName, genderIdParamName, salutationIdParamName, clientTypeIdParamName, clientClassificationIdParamName,
|
||||||
occupationCategoryIdParamName, occupationIdParamName, maritalStatusIdParamName,
|
occupationCategoryIdParamName, occupationIdParamName, maritalStatusIdParamName,
|
||||||
educationLevelIdParamName, degreeQualificationIdParamName,
|
educationLevelIdParamName, degreeQualificationIdParamName,
|
||||||
religionIdParamName, socialCategoryIdParamName, monthlyIncomeParamName, annualTurnoverParamName,
|
religionIdParamName, socialCategoryIdParamName, monthlyIncomeParamName, annualIncomeParamName, annualTurnoverParamName,
|
||||||
agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
monthlyTurnoverParamName, agricultureIncomeParamName, nonAgricultureIncomeParamName,
|
||||||
|
isSeniorCitizenParamName, isPwdParamName, pwdCategoryIdParamName,
|
||||||
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
ageYearsParamName, ageMonthsParamName, ageDaysParamName,
|
||||||
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianRelationParamName,
|
nationalityIdParamName, isMinorParamName, guardianClientIdParamName, guardianRelationParamName,
|
||||||
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
dipVerifiedParamName, dipLookupLogIdParamName, dipTimestampParamName,
|
||||||
alternateDipVerifiedParamName, alternateDipLookupLogIdParamName, alternateDipTimestampParamName,
|
alternateDipVerifiedParamName, alternateDipLookupLogIdParamName, alternateDipTimestampParamName,
|
||||||
nameVerifiedParamName, screeningLogIdParamName,
|
nameVerifiedParamName, screeningLogIdParamName,
|
||||||
submittedOnDateParamName, clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, isStaffParamName));
|
submittedOnDateParamName, clientNonPersonDetailsParamName, displaynameParamName, legalFormIdParamName, isStaffParamName,
|
||||||
|
mobileOldOtpIdParamName, mobileNewOtpIdParamName, emailOldOtpIdParamName, emailNewOtpIdParamName));
|
||||||
|
|
||||||
protected static final Set<String> CLIENT_NON_PERSON_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
protected static final Set<String> CLIENT_NON_PERSON_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||||
Arrays.asList(localeParamName, dateFormatParamName, incorpNumberParamName, remarksParamName, incorpValidityTillParamName,
|
Arrays.asList(localeParamName, dateFormatParamName, incorpNumberParamName, remarksParamName, incorpValidityTillParamName,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user