Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46f5a1babd | ||
|
|
20fe11e60d | ||
|
|
eb1aa767f7 | ||
|
|
8724c54d9b | ||
|
|
5c0a482f49 | ||
|
|
0e07e60328 | ||
|
|
e227e8ae0f | ||
|
|
231919b62b |
+9
-72
@@ -664,78 +664,6 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createAgent() {
|
|
||||||
this.actionName = "CREATE";
|
|
||||||
this.entityName = "AGENT";
|
|
||||||
this.entityId = null;
|
|
||||||
this.href = "/dds/agents/template";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder updateAgent(final Long agentId) {
|
|
||||||
this.actionName = "UPDATE";
|
|
||||||
this.entityName = "AGENT";
|
|
||||||
this.entityId = agentId;
|
|
||||||
this.href = "/dds/agents/" + agentId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder createParty() {
|
|
||||||
this.actionName = "CREATE";
|
|
||||||
this.entityName = "PARTY";
|
|
||||||
this.entityId = null;
|
|
||||||
this.href = "/dds/parties/template";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder updateParty(final Long partyId) {
|
|
||||||
this.actionName = "UPDATE";
|
|
||||||
this.entityName = "PARTY";
|
|
||||||
this.entityId = partyId;
|
|
||||||
this.href = "/dds/parties/" + partyId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder closeParty(final Long partyId) {
|
|
||||||
this.actionName = "CLOSE";
|
|
||||||
this.entityName = "PARTY";
|
|
||||||
this.entityId = partyId;
|
|
||||||
this.href = "/dds/parties/" + partyId + "?command=close";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder createPartyTransaction() {
|
|
||||||
this.actionName = "CREATE";
|
|
||||||
this.entityName = "PARTYTRANSACTION";
|
|
||||||
this.entityId = null;
|
|
||||||
this.href = "/dds/partytransactions/template";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder createCommissionSlabs() {
|
|
||||||
this.actionName = "CREATE";
|
|
||||||
this.entityName = "COMMISSIONSLAB";
|
|
||||||
this.entityId = null;
|
|
||||||
this.href = "/dds/commissionslabs/template";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder generateDdsBatchRun() {
|
|
||||||
this.actionName = "GENERATE";
|
|
||||||
this.entityName = "DDSBATCH";
|
|
||||||
this.entityId = null;
|
|
||||||
this.href = "/dds/batchruns/template";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder postDdsBatchRun(final Long batchRunId) {
|
|
||||||
this.actionName = "POST";
|
|
||||||
this.entityName = "DDSBATCH";
|
|
||||||
this.entityId = batchRunId;
|
|
||||||
this.href = "/dds/batchruns/" + batchRunId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandWrapperBuilder createOfficeTransaction() {
|
public CommandWrapperBuilder createOfficeTransaction() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "OFFICETRANSACTION";
|
this.entityName = "OFFICETRANSACTION";
|
||||||
@@ -2969,6 +2897,15 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder paymentFixedDepositAccount(final Long accountId) {
|
||||||
|
this.actionName = "PAYMENT";
|
||||||
|
this.entityName = "FIXEDDEPOSITACCOUNT";
|
||||||
|
this.entityId = accountId;
|
||||||
|
this.savingsId = accountId;
|
||||||
|
this.href = "/fixeddepositaccounts/" + accountId + "?command=payment";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder fixedDepositAccountInterestCalculation(final Long accountId) {
|
public CommandWrapperBuilder fixedDepositAccountInterestCalculation(final Long accountId) {
|
||||||
this.actionName = "CALCULATEINTEREST";
|
this.actionName = "CALCULATEINTEREST";
|
||||||
this.entityName = "FIXEDDEPOSITACCOUNT";
|
this.entityName = "FIXEDDEPOSITACCOUNT";
|
||||||
|
|||||||
+13
@@ -130,6 +130,19 @@ public class DataValidatorBuilder {
|
|||||||
this.dataValidationErrors.add(error);
|
this.dataValidationErrors.add(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #failWithCode(String, Object...)}, but the caller's own message is what's actually shown to the
|
||||||
|
* user (that method always renders the generic "Failed data validation due to: {errorCode}." regardless of the
|
||||||
|
* args passed to it).
|
||||||
|
*/
|
||||||
|
public void failWithCodeAndMessage(final String errorCode, final String defaultUserMessage,
|
||||||
|
final Object... defaultUserMessageArgs) {
|
||||||
|
String validationErrorCode = "validation.msg." + this.resource + "." + this.parameter + "." + errorCode;
|
||||||
|
final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode, defaultUserMessage, this.parameter,
|
||||||
|
this.value, defaultUserMessageArgs);
|
||||||
|
this.dataValidationErrors.add(error);
|
||||||
|
}
|
||||||
|
|
||||||
public void failWithCodeNoParameterAddedToErrorCode(final String errorCode, final Object... defaultUserMessageArgs) {
|
public void failWithCodeNoParameterAddedToErrorCode(final String errorCode, final Object... defaultUserMessageArgs) {
|
||||||
String validationErrorCode = "validation.msg." + this.resource + "." + errorCode;
|
String validationErrorCode = "validation.msg." + this.resource + "." + errorCode;
|
||||||
String defaultEnglishMessage = "Failed data validation due to: " + errorCode + ".";
|
String defaultEnglishMessage = "Failed data validation due to: " + errorCode + ".";
|
||||||
|
|||||||
-148
@@ -1,148 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immutable data object representing a DDS agent, including its collection-limit history and
|
|
||||||
* guarantors. Carries the collection/commission/security-account fields merged in from SubAgent
|
|
||||||
* (2026-08-24) - Agent now does the daily collection work directly.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public final class AgentData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final Long clientId;
|
|
||||||
private final String clientName;
|
|
||||||
private final String agentCode;
|
|
||||||
private final String agentNameId;
|
|
||||||
private final String agentAddress1;
|
|
||||||
private final String agentAddress2;
|
|
||||||
private final String agentCity;
|
|
||||||
private final String agentPinCode;
|
|
||||||
private final String agentTelephone;
|
|
||||||
private final String agentSavingAccountType;
|
|
||||||
private final String agentSavingAccountNo;
|
|
||||||
private final LocalDate agentOpeningDate;
|
|
||||||
private final LocalDate agentBirthDate;
|
|
||||||
private final BigDecimal agentCurrentBalance;
|
|
||||||
private final BigDecimal agentOpeningBalance;
|
|
||||||
private final String homeBranch;
|
|
||||||
private final String savingBranch;
|
|
||||||
private final Boolean tdsApplicable;
|
|
||||||
private final BigDecimal tdsPercent;
|
|
||||||
private final BigDecimal currentTds;
|
|
||||||
private final BigDecimal tdsAmount;
|
|
||||||
private final String transferBranch;
|
|
||||||
private final String transferAmountAccountType;
|
|
||||||
private final String transferAccountNo;
|
|
||||||
private final Boolean commissionCalculation;
|
|
||||||
private final BigDecimal limit;
|
|
||||||
private final BigDecimal machineRent;
|
|
||||||
private final String agentSecurityBranch;
|
|
||||||
private final String agentSecurityAccountType;
|
|
||||||
private final String agentSecurityAccountNo;
|
|
||||||
private final BigDecimal commission;
|
|
||||||
private final BigDecimal otherCommission;
|
|
||||||
private final BigDecimal agentDeposit;
|
|
||||||
private final BigDecimal festivalAdvance;
|
|
||||||
private final BigDecimal otherDeduction;
|
|
||||||
private final Collection<AgentLimitData> limits;
|
|
||||||
private final Collection<AgentGuarantorData> guarantors;
|
|
||||||
|
|
||||||
public static AgentData instance(final Long id, final Long clientId, final String clientName, final String agentCode,
|
|
||||||
final String agentNameId, final String agentAddress1, final String agentAddress2, final String agentCity,
|
|
||||||
final String agentPinCode, final String agentTelephone, final String agentSavingAccountType,
|
|
||||||
final String agentSavingAccountNo, final LocalDate agentOpeningDate, final LocalDate agentBirthDate,
|
|
||||||
final BigDecimal agentCurrentBalance, final BigDecimal agentOpeningBalance, final String homeBranch,
|
|
||||||
final String savingBranch, final Boolean tdsApplicable, final BigDecimal tdsPercent, final BigDecimal currentTds,
|
|
||||||
final BigDecimal tdsAmount, final String transferBranch, final String transferAmountAccountType,
|
|
||||||
final String transferAccountNo, final Boolean commissionCalculation, final BigDecimal limit, final BigDecimal machineRent,
|
|
||||||
final String agentSecurityBranch, final String agentSecurityAccountType, final String agentSecurityAccountNo,
|
|
||||||
final BigDecimal commission, final BigDecimal otherCommission, final BigDecimal agentDeposit,
|
|
||||||
final BigDecimal festivalAdvance, final BigDecimal otherDeduction, final Collection<AgentLimitData> limits,
|
|
||||||
final Collection<AgentGuarantorData> guarantors) {
|
|
||||||
return new AgentData(id, clientId, clientName, agentCode, agentNameId, agentAddress1, agentAddress2, agentCity, agentPinCode,
|
|
||||||
agentTelephone, agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, agentCurrentBalance,
|
|
||||||
agentOpeningBalance, homeBranch, savingBranch, tdsApplicable, tdsPercent, currentTds, tdsAmount, transferBranch,
|
|
||||||
transferAmountAccountType, transferAccountNo, commissionCalculation, limit, machineRent, agentSecurityBranch,
|
|
||||||
agentSecurityAccountType, agentSecurityAccountNo, commission, otherCommission, agentDeposit, festivalAdvance,
|
|
||||||
otherDeduction, limits, guarantors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentData(final Long id, final Long clientId, final String clientName, final String agentCode, final String agentNameId,
|
|
||||||
final String agentAddress1, final String agentAddress2, final String agentCity, final String agentPinCode,
|
|
||||||
final String agentTelephone, final String agentSavingAccountType, final String agentSavingAccountNo,
|
|
||||||
final LocalDate agentOpeningDate, final LocalDate agentBirthDate, final BigDecimal agentCurrentBalance,
|
|
||||||
final BigDecimal agentOpeningBalance, final String homeBranch, final String savingBranch, final Boolean tdsApplicable,
|
|
||||||
final BigDecimal tdsPercent, final BigDecimal currentTds, final BigDecimal tdsAmount, final String transferBranch,
|
|
||||||
final String transferAmountAccountType, final String transferAccountNo, final Boolean commissionCalculation,
|
|
||||||
final BigDecimal limit, final BigDecimal machineRent, final String agentSecurityBranch,
|
|
||||||
final String agentSecurityAccountType, final String agentSecurityAccountNo, final BigDecimal commission,
|
|
||||||
final BigDecimal otherCommission, final BigDecimal agentDeposit, final BigDecimal festivalAdvance,
|
|
||||||
final BigDecimal otherDeduction, final Collection<AgentLimitData> limits, final Collection<AgentGuarantorData> guarantors) {
|
|
||||||
this.id = id;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.clientName = clientName;
|
|
||||||
this.agentCode = agentCode;
|
|
||||||
this.agentNameId = agentNameId;
|
|
||||||
this.agentAddress1 = agentAddress1;
|
|
||||||
this.agentAddress2 = agentAddress2;
|
|
||||||
this.agentCity = agentCity;
|
|
||||||
this.agentPinCode = agentPinCode;
|
|
||||||
this.agentTelephone = agentTelephone;
|
|
||||||
this.agentSavingAccountType = agentSavingAccountType;
|
|
||||||
this.agentSavingAccountNo = agentSavingAccountNo;
|
|
||||||
this.agentOpeningDate = agentOpeningDate;
|
|
||||||
this.agentBirthDate = agentBirthDate;
|
|
||||||
this.agentCurrentBalance = agentCurrentBalance;
|
|
||||||
this.agentOpeningBalance = agentOpeningBalance;
|
|
||||||
this.homeBranch = homeBranch;
|
|
||||||
this.savingBranch = savingBranch;
|
|
||||||
this.tdsApplicable = tdsApplicable;
|
|
||||||
this.tdsPercent = tdsPercent;
|
|
||||||
this.currentTds = currentTds;
|
|
||||||
this.tdsAmount = tdsAmount;
|
|
||||||
this.transferBranch = transferBranch;
|
|
||||||
this.transferAmountAccountType = transferAmountAccountType;
|
|
||||||
this.transferAccountNo = transferAccountNo;
|
|
||||||
this.commissionCalculation = commissionCalculation;
|
|
||||||
this.limit = limit;
|
|
||||||
this.machineRent = machineRent;
|
|
||||||
this.agentSecurityBranch = agentSecurityBranch;
|
|
||||||
this.agentSecurityAccountType = agentSecurityAccountType;
|
|
||||||
this.agentSecurityAccountNo = agentSecurityAccountNo;
|
|
||||||
this.commission = commission;
|
|
||||||
this.otherCommission = otherCommission;
|
|
||||||
this.agentDeposit = agentDeposit;
|
|
||||||
this.festivalAdvance = festivalAdvance;
|
|
||||||
this.otherDeduction = otherDeduction;
|
|
||||||
this.limits = limits;
|
|
||||||
this.guarantors = guarantors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immutable data object representing a single agent guarantor.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public final class AgentGuarantorData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final Long clientId;
|
|
||||||
private final String guarantorName;
|
|
||||||
private final String address1;
|
|
||||||
private final String address2;
|
|
||||||
private final String city;
|
|
||||||
private final String pinCode;
|
|
||||||
private final String telephone;
|
|
||||||
|
|
||||||
public static AgentGuarantorData instance(final Long id, final Long clientId, final String guarantorName, final String address1,
|
|
||||||
final String address2, final String city, final String pinCode, final String telephone) {
|
|
||||||
return new AgentGuarantorData(id, clientId, guarantorName, address1, address2, city, pinCode, telephone);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentGuarantorData(final Long id, final Long clientId, final String guarantorName, final String address1,
|
|
||||||
final String address2, final String city, final String pinCode, final String telephone) {
|
|
||||||
this.id = id;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.guarantorName = guarantorName;
|
|
||||||
this.address1 = address1;
|
|
||||||
this.address2 = address2;
|
|
||||||
this.city = city;
|
|
||||||
this.pinCode = pinCode;
|
|
||||||
this.telephone = telephone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public final class AgentLimitData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final LocalDate fromDate;
|
|
||||||
private final LocalDate toDate;
|
|
||||||
private final BigDecimal limit;
|
|
||||||
|
|
||||||
public static AgentLimitData instance(final Long id, final LocalDate fromDate, final LocalDate toDate, final BigDecimal limit) {
|
|
||||||
return new AgentLimitData(id, fromDate, toDate, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AgentLimitData(final Long id, final LocalDate fromDate, final LocalDate toDate, final BigDecimal limit) {
|
|
||||||
this.id = id;
|
|
||||||
this.fromDate = fromDate;
|
|
||||||
this.toDate = toDate;
|
|
||||||
this.limit = limit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
import jakarta.persistence.OrderBy;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.UniqueConstraint;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A DDS (Daily Deposit Scheme) collection agent. Can represent either a person or, at the top of the
|
|
||||||
* hierarchy, the bank itself acting as its own agent. Holds its own settlement savings account and one
|
|
||||||
* or more guarantors.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_agent", uniqueConstraints = { @UniqueConstraint(columnNames = { "agent_code" }, name = "agent_code_UNIQUE") })
|
|
||||||
public class Agent extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "client_id", nullable = false)
|
|
||||||
private Client client;
|
|
||||||
|
|
||||||
@Column(name = "agent_code", length = 20, nullable = false)
|
|
||||||
private String agentCode;
|
|
||||||
|
|
||||||
@Column(name = "agent_name", length = 100, nullable = false)
|
|
||||||
private String agentName;
|
|
||||||
|
|
||||||
@Column(name = "agent_address1", length = 200, nullable = false)
|
|
||||||
private String agentAddress1;
|
|
||||||
|
|
||||||
@Column(name = "agent_address2", length = 200)
|
|
||||||
private String agentAddress2;
|
|
||||||
|
|
||||||
@Column(name = "agent_city", length = 100, nullable = false)
|
|
||||||
private String agentCity;
|
|
||||||
|
|
||||||
@Column(name = "agent_pincode", length = 20, nullable = false)
|
|
||||||
private String agentPinCode;
|
|
||||||
|
|
||||||
@Column(name = "agent_telephone", length = 20)
|
|
||||||
private String agentTelephone;
|
|
||||||
|
|
||||||
@Column(name = "agent_saving_account_type", length = 100, nullable = false)
|
|
||||||
private String agentSavingAccountType;
|
|
||||||
|
|
||||||
@Column(name = "agent_saving_account_no", length = 50, nullable = false)
|
|
||||||
private String agentSavingAccountNo;
|
|
||||||
|
|
||||||
@Column(name = "agent_opening_date", nullable = false)
|
|
||||||
private LocalDate agentOpeningDate;
|
|
||||||
|
|
||||||
@Column(name = "agent_birth_date", nullable = false)
|
|
||||||
private LocalDate agentBirthDate;
|
|
||||||
|
|
||||||
@Column(name = "agent_current_balance", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal agentCurrentBalance;
|
|
||||||
|
|
||||||
@Column(name = "agent_opening_balance", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal agentOpeningBalance;
|
|
||||||
|
|
||||||
// Fields below were merged in from SubAgent (2026-08-24) - the Agent itself now does the daily
|
|
||||||
// collection work directly; SubAgent no longer exists as a separate tier. Ag.Level / Upper Ag.Level
|
|
||||||
// / Collector (MLM) / CRPB Points / Level Update Date were dropped - they only ever meant anything
|
|
||||||
// for a multi-level hierarchy, which no longer exists here.
|
|
||||||
@Column(name = "home_branch", length = 100, nullable = false)
|
|
||||||
private String homeBranch;
|
|
||||||
|
|
||||||
@Column(name = "saving_branch", length = 100, nullable = false)
|
|
||||||
private String savingBranch;
|
|
||||||
|
|
||||||
@Column(name = "tds_applicable", nullable = false)
|
|
||||||
private boolean tdsApplicable;
|
|
||||||
|
|
||||||
@Column(name = "tds_percent", scale = 6, precision = 19)
|
|
||||||
private BigDecimal tdsPercent;
|
|
||||||
|
|
||||||
@Column(name = "current_tds", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal currentTds;
|
|
||||||
|
|
||||||
@Column(name = "tds_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal tdsAmount;
|
|
||||||
|
|
||||||
@Column(name = "transfer_branch", length = 100)
|
|
||||||
private String transferBranch;
|
|
||||||
|
|
||||||
@Column(name = "transfer_amount_account_type", length = 100)
|
|
||||||
private String transferAmountAccountType;
|
|
||||||
|
|
||||||
@Column(name = "transfer_account_no", length = 50)
|
|
||||||
private String transferAccountNo;
|
|
||||||
|
|
||||||
@Column(name = "collector_commission_calculation", nullable = false)
|
|
||||||
private boolean commissionCalculation;
|
|
||||||
|
|
||||||
@Column(name = "collector_limit_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal limitAmount;
|
|
||||||
|
|
||||||
@Column(name = "machine_rent", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal machineRent;
|
|
||||||
|
|
||||||
@Column(name = "agent_security_branch", length = 100)
|
|
||||||
private String agentSecurityBranch;
|
|
||||||
|
|
||||||
@Column(name = "agent_security_account_type", length = 100)
|
|
||||||
private String agentSecurityAccountType;
|
|
||||||
|
|
||||||
@Column(name = "agent_security_account_no", length = 50)
|
|
||||||
private String agentSecurityAccountNo;
|
|
||||||
|
|
||||||
@Column(name = "commission", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal commission;
|
|
||||||
|
|
||||||
@Column(name = "other_commission", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal otherCommission;
|
|
||||||
|
|
||||||
@Column(name = "agent_deposit", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal agentDeposit;
|
|
||||||
|
|
||||||
@Column(name = "festival_advance", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal festivalAdvance;
|
|
||||||
|
|
||||||
@Column(name = "other_deduction", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal otherDeduction;
|
|
||||||
|
|
||||||
@Column(name = "last_commission_date")
|
|
||||||
private LocalDate lastCommissionDate;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
@OrderBy("id")
|
|
||||||
private List<AgentGuarantor> guarantors = new ArrayList<>();
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "agent", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
@OrderBy("id")
|
|
||||||
private List<AgentLimit> limits = new ArrayList<>();
|
|
||||||
|
|
||||||
protected Agent() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
private Agent(final Client client, final String agentCode, final String agentName, final String agentAddress1,
|
|
||||||
final String agentAddress2, final String agentCity, final String agentPinCode, final String agentTelephone,
|
|
||||||
final String agentSavingAccountType, final String agentSavingAccountNo, final LocalDate agentOpeningDate,
|
|
||||||
final LocalDate agentBirthDate, final BigDecimal agentOpeningBalance, final JsonCommand command) {
|
|
||||||
this.client = client;
|
|
||||||
this.agentCode = agentCode;
|
|
||||||
this.agentName = agentName;
|
|
||||||
this.agentAddress1 = agentAddress1;
|
|
||||||
this.agentAddress2 = StringUtils.defaultIfEmpty(agentAddress2, null);
|
|
||||||
this.agentCity = agentCity;
|
|
||||||
this.agentPinCode = agentPinCode;
|
|
||||||
this.agentTelephone = StringUtils.defaultIfEmpty(agentTelephone, null);
|
|
||||||
this.agentSavingAccountType = agentSavingAccountType;
|
|
||||||
this.agentSavingAccountNo = agentSavingAccountNo;
|
|
||||||
this.agentOpeningDate = agentOpeningDate;
|
|
||||||
this.agentBirthDate = agentBirthDate;
|
|
||||||
this.agentOpeningBalance = agentOpeningBalance == null ? BigDecimal.ZERO : agentOpeningBalance;
|
|
||||||
this.agentCurrentBalance = this.agentOpeningBalance;
|
|
||||||
|
|
||||||
this.homeBranch = command.stringValueOfParameterNamed("homeBranch");
|
|
||||||
this.savingBranch = command.stringValueOfParameterNamed("savingBranch");
|
|
||||||
this.tdsApplicable = command.booleanPrimitiveValueOfParameterNamed("tdsApplicable");
|
|
||||||
this.tdsPercent = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("tdsPercent");
|
|
||||||
this.currentTds = BigDecimal.ZERO;
|
|
||||||
this.tdsAmount = BigDecimal.ZERO;
|
|
||||||
this.transferBranch = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferBranch"), null);
|
|
||||||
this.transferAmountAccountType = StringUtils
|
|
||||||
.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferAmountAccountType"), null);
|
|
||||||
this.transferAccountNo = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("transferAccountNo"), null);
|
|
||||||
this.commissionCalculation = command.booleanPrimitiveValueOfParameterNamed("commissionCalculation");
|
|
||||||
this.limitAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("limit"));
|
|
||||||
this.machineRent = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("machineRent"));
|
|
||||||
this.agentSecurityBranch = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityBranch"),
|
|
||||||
null);
|
|
||||||
this.agentSecurityAccountType = StringUtils
|
|
||||||
.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityAccountType"), null);
|
|
||||||
this.agentSecurityAccountNo = StringUtils.defaultIfEmpty(command.stringValueOfParameterNamedAllowingNull("agentSecurityAccountNo"),
|
|
||||||
null);
|
|
||||||
this.commission = BigDecimal.ZERO;
|
|
||||||
this.otherCommission = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("otherCommission"));
|
|
||||||
this.agentDeposit = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("agentDeposit"));
|
|
||||||
this.festivalAdvance = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("festivalAdvance"));
|
|
||||||
this.otherDeduction = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("otherDeduction"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Agent fromJson(final Client client, final String agentCode, final JsonCommand command) {
|
|
||||||
final String agentName = command.stringValueOfParameterNamed("agentNameId");
|
|
||||||
final String agentAddress1 = command.stringValueOfParameterNamed("agentAddress1");
|
|
||||||
final String agentAddress2 = command.stringValueOfParameterNamedAllowingNull("agentAddress2");
|
|
||||||
final String agentCity = command.stringValueOfParameterNamed("agentCity");
|
|
||||||
final String agentPinCode = command.stringValueOfParameterNamed("agentPinCode");
|
|
||||||
final String agentTelephone = command.stringValueOfParameterNamedAllowingNull("agentTelephone");
|
|
||||||
final String agentSavingAccountType = command.stringValueOfParameterNamed("agentSavingAccountType");
|
|
||||||
final String agentSavingAccountNo = command.stringValueOfParameterNamed("agentSavingAccountNo");
|
|
||||||
final LocalDate agentOpeningDate = command.localDateValueOfParameterNamed("agentOpeningDate");
|
|
||||||
final LocalDate agentBirthDate = command.localDateValueOfParameterNamed("agentBirthDate");
|
|
||||||
final BigDecimal agentOpeningBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("agentOpeningBalance");
|
|
||||||
|
|
||||||
return new Agent(client, agentCode, agentName, agentAddress1, agentAddress2, agentCity, agentPinCode, agentTelephone,
|
|
||||||
agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, agentOpeningBalance, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal orZero(final BigDecimal value) {
|
|
||||||
return value == null ? BigDecimal.ZERO : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addGuarantor(final AgentGuarantor guarantor) {
|
|
||||||
guarantor.setAgent(this);
|
|
||||||
this.guarantors.add(guarantor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetGuarantors() {
|
|
||||||
this.guarantors.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addLimit(final AgentLimit limit) {
|
|
||||||
limit.setAgent(this);
|
|
||||||
this.limits.add(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetLimits() {
|
|
||||||
this.limits.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a Commission Posting's amount to this agent's running commission total and stamps the date
|
|
||||||
* commission was last calculated through, so the next Commission Calculation batch only covers
|
|
||||||
* collections since then.
|
|
||||||
*/
|
|
||||||
public void postCommission(final BigDecimal amount, final LocalDate throughDate) {
|
|
||||||
this.commission = this.commission.add(amount);
|
|
||||||
this.lastCommissionDate = throughDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cash this agent has collected from parties but not yet handed over to a branch (an Agent Tran
|
|
||||||
* settlement). Raised by a Party Tran collection, lowered by an Agent Tran settlement - this is the
|
|
||||||
* figure an Agent's collection limit is enforced against.
|
|
||||||
*/
|
|
||||||
public BigDecimal credit(final BigDecimal amount) {
|
|
||||||
this.agentCurrentBalance = this.agentCurrentBalance.add(amount);
|
|
||||||
return this.agentCurrentBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal settle(final BigDecimal amount) {
|
|
||||||
this.agentCurrentBalance = this.agentCurrentBalance.subtract(amount);
|
|
||||||
return this.agentCurrentBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changeClient(final Client newClient) {
|
|
||||||
this.client = newClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long clientId() {
|
|
||||||
return this.client.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> update(final JsonCommand command) {
|
|
||||||
|
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
|
|
||||||
|
|
||||||
if (command.isChangeInLongParameterNamed("clientId", this.client.getId())) {
|
|
||||||
actualChanges.put("clientId", command.longValueOfParameterNamed("clientId"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentNameId", this.agentName)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentNameId");
|
|
||||||
actualChanges.put("agentNameId", newValue);
|
|
||||||
this.agentName = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentAddress1", this.agentAddress1)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentAddress1");
|
|
||||||
actualChanges.put("agentAddress1", newValue);
|
|
||||||
this.agentAddress1 = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentAddress2", this.agentAddress2)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentAddress2");
|
|
||||||
actualChanges.put("agentAddress2", newValue);
|
|
||||||
this.agentAddress2 = StringUtils.defaultIfEmpty(newValue, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentCity", this.agentCity)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentCity");
|
|
||||||
actualChanges.put("agentCity", newValue);
|
|
||||||
this.agentCity = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentPinCode", this.agentPinCode)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentPinCode");
|
|
||||||
actualChanges.put("agentPinCode", newValue);
|
|
||||||
this.agentPinCode = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentTelephone", this.agentTelephone)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentTelephone");
|
|
||||||
actualChanges.put("agentTelephone", newValue);
|
|
||||||
this.agentTelephone = StringUtils.defaultIfEmpty(newValue, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentSavingAccountType", this.agentSavingAccountType)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentSavingAccountType");
|
|
||||||
actualChanges.put("agentSavingAccountType", newValue);
|
|
||||||
this.agentSavingAccountType = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInStringParameterNamed("agentSavingAccountNo", this.agentSavingAccountNo)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed("agentSavingAccountNo");
|
|
||||||
actualChanges.put("agentSavingAccountNo", newValue);
|
|
||||||
this.agentSavingAccountNo = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("agentOpeningDate", this.agentOpeningDate)) {
|
|
||||||
actualChanges.put("agentOpeningDate", command.stringValueOfParameterNamed("agentOpeningDate"));
|
|
||||||
this.agentOpeningDate = command.localDateValueOfParameterNamed("agentOpeningDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("agentBirthDate", this.agentBirthDate)) {
|
|
||||||
actualChanges.put("agentBirthDate", command.stringValueOfParameterNamed("agentBirthDate"));
|
|
||||||
this.agentBirthDate = command.localDateValueOfParameterNamed("agentBirthDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "homeBranch", this.homeBranch, v -> this.homeBranch = v);
|
|
||||||
stringChange(command, actualChanges, "savingBranch", this.savingBranch, v -> this.savingBranch = v);
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("tdsApplicable", this.tdsApplicable)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("tdsApplicable");
|
|
||||||
actualChanges.put("tdsApplicable", newValue);
|
|
||||||
this.tdsApplicable = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("tdsPercent", this.tdsPercent)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("tdsPercent");
|
|
||||||
actualChanges.put("tdsPercent", newValue);
|
|
||||||
this.tdsPercent = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "transferBranch", this.transferBranch,
|
|
||||||
v -> this.transferBranch = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
stringChange(command, actualChanges, "transferAmountAccountType", this.transferAmountAccountType,
|
|
||||||
v -> this.transferAmountAccountType = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
stringChange(command, actualChanges, "transferAccountNo", this.transferAccountNo,
|
|
||||||
v -> this.transferAccountNo = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("commissionCalculation", this.commissionCalculation)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("commissionCalculation");
|
|
||||||
actualChanges.put("commissionCalculation", newValue);
|
|
||||||
this.commissionCalculation = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("limit", this.limitAmount)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("limit");
|
|
||||||
actualChanges.put("limit", newValue);
|
|
||||||
this.limitAmount = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("machineRent", this.machineRent)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("machineRent");
|
|
||||||
actualChanges.put("machineRent", newValue);
|
|
||||||
this.machineRent = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "agentSecurityBranch", this.agentSecurityBranch,
|
|
||||||
v -> this.agentSecurityBranch = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
stringChange(command, actualChanges, "agentSecurityAccountType", this.agentSecurityAccountType,
|
|
||||||
v -> this.agentSecurityAccountType = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
stringChange(command, actualChanges, "agentSecurityAccountNo", this.agentSecurityAccountNo,
|
|
||||||
v -> this.agentSecurityAccountNo = StringUtils.defaultIfEmpty(v, null));
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("otherCommission", this.otherCommission)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("otherCommission");
|
|
||||||
actualChanges.put("otherCommission", newValue);
|
|
||||||
this.otherCommission = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("agentDeposit", this.agentDeposit)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("agentDeposit");
|
|
||||||
actualChanges.put("agentDeposit", newValue);
|
|
||||||
this.agentDeposit = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("festivalAdvance", this.festivalAdvance)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("festivalAdvance");
|
|
||||||
actualChanges.put("festivalAdvance", newValue);
|
|
||||||
this.festivalAdvance = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("otherDeduction", this.otherDeduction)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("otherDeduction");
|
|
||||||
actualChanges.put("otherDeduction", newValue);
|
|
||||||
this.otherDeduction = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return actualChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stringChange(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
|
|
||||||
final String existingValue, final java.util.function.Consumer<String> setter) {
|
|
||||||
if (command.isChangeInStringParameterNamed(paramName, existingValue)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed(paramName);
|
|
||||||
actualChanges.put(paramName, newValue);
|
|
||||||
setter.accept(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-83
@@ -1,83 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single guarantor recorded against an {@link Agent} (or, reused with the same shape, against a
|
|
||||||
* SubAgent). Always created/replaced as part of its parent agent's own create/update command - it has
|
|
||||||
* no independent API of its own.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_agent_guarantor")
|
|
||||||
public class AgentGuarantor extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "agent_id", nullable = false)
|
|
||||||
private Agent agent;
|
|
||||||
|
|
||||||
@Column(name = "client_id", nullable = false)
|
|
||||||
private Long clientId;
|
|
||||||
|
|
||||||
@Column(name = "guarantor_name", length = 100, nullable = false)
|
|
||||||
private String guarantorName;
|
|
||||||
|
|
||||||
@Column(name = "address1", length = 200, nullable = false)
|
|
||||||
private String address1;
|
|
||||||
|
|
||||||
@Column(name = "address2", length = 200)
|
|
||||||
private String address2;
|
|
||||||
|
|
||||||
@Column(name = "city", length = 100, nullable = false)
|
|
||||||
private String city;
|
|
||||||
|
|
||||||
@Column(name = "pin_code", length = 20, nullable = false)
|
|
||||||
private String pinCode;
|
|
||||||
|
|
||||||
@Column(name = "telephone", length = 20)
|
|
||||||
private String telephone;
|
|
||||||
|
|
||||||
protected AgentGuarantor() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentGuarantor(final Long clientId, final String guarantorName, final String address1, final String address2,
|
|
||||||
final String city, final String pinCode, final String telephone) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.guarantorName = guarantorName;
|
|
||||||
this.address1 = address1;
|
|
||||||
this.address2 = StringUtils.defaultIfEmpty(address2, null);
|
|
||||||
this.city = city;
|
|
||||||
this.pinCode = pinCode;
|
|
||||||
this.telephone = StringUtils.defaultIfEmpty(telephone, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAgent(final Agent agent) {
|
|
||||||
this.agent = agent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-66
@@ -1,66 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.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 lombok.Getter;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One row of an Agent's time-bound collection-limit history (From Date / To Date / Limit) - an
|
|
||||||
* Agent's authorized collection limit can change over time and every change is kept, not overwritten.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_agent_limit")
|
|
||||||
public class AgentLimit extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "agent_id", nullable = false)
|
|
||||||
private Agent agent;
|
|
||||||
|
|
||||||
@Column(name = "from_date", nullable = false)
|
|
||||||
private LocalDate fromDate;
|
|
||||||
|
|
||||||
@Column(name = "to_date", nullable = false)
|
|
||||||
private LocalDate toDate;
|
|
||||||
|
|
||||||
@Column(name = "limit_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal limitAmount;
|
|
||||||
|
|
||||||
protected AgentLimit() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentLimit(final LocalDate fromDate, final LocalDate toDate, final BigDecimal limitAmount) {
|
|
||||||
this.fromDate = fromDate;
|
|
||||||
this.toDate = toDate;
|
|
||||||
this.limitAmount = limitAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAgent(final Agent agent) {
|
|
||||||
this.agent = agent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RuntimeException} thrown when a DDS agent resource is not found.
|
|
||||||
*/
|
|
||||||
public class AgentNotFoundException extends AbstractPlatformResourceNotFoundException {
|
|
||||||
|
|
||||||
public AgentNotFoundException(final Long id) {
|
|
||||||
super("error.msg.agent.id.invalid", "Agent with identifier " + id + " does not exist", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentNotFoundException(final Long id, final EmptyResultDataAccessException e) {
|
|
||||||
super("error.msg.agent.id.invalid", "Agent with identifier " + id + " does not exist", id, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AgentNotFoundException(final String agentCode) {
|
|
||||||
super("error.msg.agent.code.invalid", "Agent with code " + agentCode + " does not exist", agentCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Builder
|
|
||||||
public final class CommissionSlabData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final String slabType;
|
|
||||||
private final BigDecimal slabFrom;
|
|
||||||
private final BigDecimal slabTo;
|
|
||||||
private final BigDecimal commissionPercent;
|
|
||||||
}
|
|
||||||
-61
@@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One row of the agent commission chart: an ACCOUNT slab (commission percentage on collection
|
|
||||||
* volume) or a COMMISSION slab (the override a senior agent earns on a sub-agent's commission).
|
|
||||||
* Append-only reference data, like the legacy screen it replaces - there is deliberately no update or
|
|
||||||
* delete API; a superseded slab range is retired by adding a new row, not editing history.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_commission_slab")
|
|
||||||
public class CommissionSlab extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@Column(name = "slab_type", length = 20, nullable = false)
|
|
||||||
private String slabType;
|
|
||||||
|
|
||||||
@Column(name = "slab_from", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal slabFrom;
|
|
||||||
|
|
||||||
@Column(name = "slab_to", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal slabTo;
|
|
||||||
|
|
||||||
@Column(name = "commission_percent", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal commissionPercent;
|
|
||||||
|
|
||||||
protected CommissionSlab() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommissionSlab(final String slabType, final BigDecimal slabFrom, final BigDecimal slabTo, final BigDecimal commissionPercent) {
|
|
||||||
this.slabType = slabType;
|
|
||||||
this.slabFrom = slabFrom;
|
|
||||||
this.slabTo = slabTo;
|
|
||||||
this.commissionPercent = commissionPercent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+16
-1
@@ -72,11 +72,26 @@ public class SavingsAccountSummaryData {
|
|||||||
// differentiate deposit accounts Savings, FD and RD accounts
|
// differentiate deposit accounts Savings, FD and RD accounts
|
||||||
private final EnumOptionData depositType;
|
private final EnumOptionData depositType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a Fixed Deposit, the stable customer-facing FD number: the account number of the first account in the renewal
|
||||||
|
* chain, which stays the same across every renewal even though each term is its own account row. Equals accountNo
|
||||||
|
* for an FD that has never been renewed, and is null for non-deposit accounts.
|
||||||
|
*/
|
||||||
|
private final String fdAccountNumber;
|
||||||
|
/**
|
||||||
|
* Which renewal cycle of that FD this row is: 0 for the originally opened account, 1 for the first renewal, and so
|
||||||
|
* on. Lets a client's account list collapse a renewal chain into one FD instead of listing each term separately.
|
||||||
|
*/
|
||||||
|
private final Integer renewalCycleNo;
|
||||||
|
|
||||||
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
||||||
final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
|
final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
|
||||||
final BigDecimal accountBalance, final BigDecimal onHoldFunds, final BigDecimal savingsAmountOnHold,
|
final BigDecimal accountBalance, final BigDecimal onHoldFunds, final BigDecimal savingsAmountOnHold,
|
||||||
final BigDecimal availableBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline,
|
final BigDecimal availableBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline,
|
||||||
final EnumOptionData depositType, final SavingsAccountSubStatusEnumData subStatus, final LocalDate lastActiveTransactionDate) {
|
final EnumOptionData depositType, final SavingsAccountSubStatusEnumData subStatus, final LocalDate lastActiveTransactionDate,
|
||||||
|
final String fdAccountNumber, final Integer renewalCycleNo) {
|
||||||
|
this.fdAccountNumber = fdAccountNumber;
|
||||||
|
this.renewalCycleNo = renewalCycleNo;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.accountNo = accountNo;
|
this.accountNo = accountNo;
|
||||||
this.externalId = externalId;
|
this.externalId = externalId;
|
||||||
|
|||||||
-50
@@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Builder(toBuilder = true)
|
|
||||||
public final class BatchRunData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final String jobType;
|
|
||||||
private final String branchScope;
|
|
||||||
private final LocalDate jobDate;
|
|
||||||
private final Long agentFrom;
|
|
||||||
private final Long agentTo;
|
|
||||||
private final Long partyFrom;
|
|
||||||
private final Long partyTo;
|
|
||||||
private final String status;
|
|
||||||
private final Integer totalRows;
|
|
||||||
private final BigDecimal totalAmount;
|
|
||||||
private final LocalDate generatedOnDate;
|
|
||||||
private final LocalDate postedOnDate;
|
|
||||||
private final Collection<BatchRunRowData> rows;
|
|
||||||
}
|
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Builder
|
|
||||||
public final class BatchRunRowData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final Long partyId;
|
|
||||||
private final String partyCode;
|
|
||||||
private final String partyName;
|
|
||||||
private final Long agentId;
|
|
||||||
private final String agentCode;
|
|
||||||
private final BigDecimal amount;
|
|
||||||
}
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
import jakarta.persistence.OrderBy;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One Calculate-then-Post batch run - the deliberate two-step control from the legacy Interest Part
|
|
||||||
* menu (Interest Calculation -> Interest Posting, OD Interest Calculation -> OD Interest Posting,
|
|
||||||
* Commission Calculation -> Commission Posting): a run is generated (rows computed and saved as a
|
|
||||||
* preview, nothing else changes), then posted (a separate, explicit step that applies exactly the
|
|
||||||
* rows that were previewed - never a fresh recalculation, so what gets applied is always what was
|
|
||||||
* seen).
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_batch_run")
|
|
||||||
public class BatchRun extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@Column(name = "job_type", length = 40, nullable = false)
|
|
||||||
private String jobType;
|
|
||||||
|
|
||||||
@Column(name = "branch_scope", length = 20, nullable = false)
|
|
||||||
private String branchScope;
|
|
||||||
|
|
||||||
@Column(name = "job_date", nullable = false)
|
|
||||||
private LocalDate jobDate;
|
|
||||||
|
|
||||||
@Column(name = "agent_from", nullable = false)
|
|
||||||
private Long agentFrom;
|
|
||||||
|
|
||||||
@Column(name = "agent_to", nullable = false)
|
|
||||||
private Long agentTo;
|
|
||||||
|
|
||||||
@Column(name = "party_from")
|
|
||||||
private Long partyFrom;
|
|
||||||
|
|
||||||
@Column(name = "party_to")
|
|
||||||
private Long partyTo;
|
|
||||||
|
|
||||||
@Column(name = "status", length = 20, nullable = false)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Column(name = "total_rows", nullable = false)
|
|
||||||
private Integer totalRows;
|
|
||||||
|
|
||||||
@Column(name = "total_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal totalAmount;
|
|
||||||
|
|
||||||
@Column(name = "generated_on_date", nullable = false)
|
|
||||||
private LocalDate generatedOnDate;
|
|
||||||
|
|
||||||
@Column(name = "posted_on_date")
|
|
||||||
private LocalDate postedOnDate;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "batchRun", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
@OrderBy("id")
|
|
||||||
private List<BatchRunRow> rows = new ArrayList<>();
|
|
||||||
|
|
||||||
protected BatchRun() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatchRun(final String jobType, final String branchScope, final LocalDate jobDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo, final LocalDate generatedOnDate) {
|
|
||||||
this.jobType = jobType;
|
|
||||||
this.branchScope = branchScope;
|
|
||||||
this.jobDate = jobDate;
|
|
||||||
this.agentFrom = agentFrom;
|
|
||||||
this.agentTo = agentTo;
|
|
||||||
this.partyFrom = partyFrom;
|
|
||||||
this.partyTo = partyTo;
|
|
||||||
this.status = "CALCULATED";
|
|
||||||
this.totalRows = 0;
|
|
||||||
this.totalAmount = BigDecimal.ZERO;
|
|
||||||
this.generatedOnDate = generatedOnDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addRow(final BatchRunRow row) {
|
|
||||||
row.setBatchRun(this);
|
|
||||||
this.rows.add(row);
|
|
||||||
this.totalRows = this.rows.size();
|
|
||||||
this.totalAmount = this.totalAmount.add(row.getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPosted() {
|
|
||||||
return "POSTED".equals(this.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markPosted(final LocalDate postedOnDate) {
|
|
||||||
this.status = "POSTED";
|
|
||||||
this.postedOnDate = postedOnDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-65
@@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.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 lombok.Getter;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One previewed/posted line of a {@link BatchRun}: for an interest job, one Party's accrued interest;
|
|
||||||
* for a commission job, one Agent's commission on their collected volume (partyId is null there).
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_batch_run_row")
|
|
||||||
public class BatchRunRow extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "batch_run_id", nullable = false)
|
|
||||||
private BatchRun batchRun;
|
|
||||||
|
|
||||||
@Column(name = "party_id")
|
|
||||||
private Long partyId;
|
|
||||||
|
|
||||||
@Column(name = "agent_id", nullable = false)
|
|
||||||
private Long agentId;
|
|
||||||
|
|
||||||
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal amount;
|
|
||||||
|
|
||||||
protected BatchRunRow() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public BatchRunRow(final Long partyId, final Long agentId, final BigDecimal amount) {
|
|
||||||
this.partyId = partyId;
|
|
||||||
this.agentId = agentId;
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBatchRun(final BatchRun batchRun) {
|
|
||||||
this.batchRun = batchRun;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when Post is attempted on a batch run that has already been posted - a run applies once,
|
|
||||||
* matching the calculate-then-post safety control (never re-post the same preview).
|
|
||||||
*/
|
|
||||||
public class BatchRunAlreadyPostedException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public BatchRunAlreadyPostedException(final Long batchRunId) {
|
|
||||||
super("error.msg.ddsbatchrun.already.posted", "DDS batch run " + batchRunId + " has already been posted", batchRunId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
|
||||||
|
|
||||||
public class BatchRunNotFoundException extends AbstractPlatformResourceNotFoundException {
|
|
||||||
|
|
||||||
public BatchRunNotFoundException(final Long id) {
|
|
||||||
super("error.msg.ddsbatchrun.id.invalid", "DDS batch run with identifier " + id + " does not exist", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-107
@@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immutable data object representing a DDS Party (customer scheme account), including its nominees.
|
|
||||||
* Built via the Lombok builder rather than a long positional constructor - the field count here makes
|
|
||||||
* positional args unreadable and error-prone to keep in sync.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Builder(toBuilder = true)
|
|
||||||
public final class PartyData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final Long clientId;
|
|
||||||
private final String clientName;
|
|
||||||
private final Long agentId;
|
|
||||||
private final String agentCode;
|
|
||||||
private final String agentName;
|
|
||||||
private final Long savingsAccountId;
|
|
||||||
private final String subType;
|
|
||||||
private final String partyCode;
|
|
||||||
private final String lastAccount;
|
|
||||||
private final Boolean nominal;
|
|
||||||
private final String aadharNo;
|
|
||||||
private final String partyName;
|
|
||||||
private final String partyName1;
|
|
||||||
private final String address1;
|
|
||||||
private final String address2;
|
|
||||||
private final String address3;
|
|
||||||
private final String city;
|
|
||||||
private final String pincode;
|
|
||||||
private final String gender;
|
|
||||||
private final String panCardNo;
|
|
||||||
private final String telephone;
|
|
||||||
private final String mobileNoSms;
|
|
||||||
private final String operation;
|
|
||||||
private final LocalDate birthDate;
|
|
||||||
private final String occupation;
|
|
||||||
private final String risk;
|
|
||||||
private final String ckycId;
|
|
||||||
private final String adultMinor;
|
|
||||||
private final LocalDate minorDate;
|
|
||||||
private final Long guardianClientId;
|
|
||||||
private final String minAccountOpt;
|
|
||||||
private final String relation;
|
|
||||||
private final LocalDate openingDate;
|
|
||||||
private final Integer month;
|
|
||||||
private final LocalDate maturityDate;
|
|
||||||
private final LocalDate reopenDate;
|
|
||||||
private final BigDecimal depositAmount;
|
|
||||||
private final BigDecimal dailyDepositAmount;
|
|
||||||
private final Integer days;
|
|
||||||
private final String ledgerNo;
|
|
||||||
private final BigDecimal maturityAmount;
|
|
||||||
private final BigDecimal interestRate;
|
|
||||||
private final String withdrawType;
|
|
||||||
private final Boolean od1Flag;
|
|
||||||
private final Boolean od2Flag;
|
|
||||||
private final Boolean lien;
|
|
||||||
private final LocalDate payDate;
|
|
||||||
private final BigDecimal paymentAmount;
|
|
||||||
private final BigDecimal currentBalance;
|
|
||||||
private final BigDecimal openingBalance;
|
|
||||||
private final Boolean isSmsActive;
|
|
||||||
private final Boolean isEmailActive;
|
|
||||||
private final BigDecimal interestCalc;
|
|
||||||
private final LocalDate interestDate;
|
|
||||||
private final String spDescription;
|
|
||||||
private final String freezeCode;
|
|
||||||
private final String advCode;
|
|
||||||
private final String creditToBranch;
|
|
||||||
private final String creditToAccountType;
|
|
||||||
private final String creditToAccount;
|
|
||||||
private final Boolean nomineeApplicable;
|
|
||||||
private final String nomineeSplitMode;
|
|
||||||
private final String status;
|
|
||||||
private final LocalDate closeDate;
|
|
||||||
private final Collection<PartyNomineeData> nominees;
|
|
||||||
}
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public final class PartyNomineeData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final LocalDate nominationDate;
|
|
||||||
private final String name;
|
|
||||||
private final String mobileNo;
|
|
||||||
private final LocalDate birthDate;
|
|
||||||
private final Boolean minor;
|
|
||||||
private final String relation;
|
|
||||||
private final String gender;
|
|
||||||
private final String guardianName;
|
|
||||||
|
|
||||||
public static PartyNomineeData instance(final Long id, final LocalDate nominationDate, final String name, final String mobileNo,
|
|
||||||
final LocalDate birthDate, final Boolean minor, final String relation, final String gender, final String guardianName) {
|
|
||||||
return new PartyNomineeData(id, nominationDate, name, mobileNo, birthDate, minor, relation, gender, guardianName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PartyNomineeData(final Long id, final LocalDate nominationDate, final String name, final String mobileNo,
|
|
||||||
final LocalDate birthDate, final Boolean minor, final String relation, final String gender, final String guardianName) {
|
|
||||||
this.id = id;
|
|
||||||
this.nominationDate = nominationDate;
|
|
||||||
this.name = name;
|
|
||||||
this.mobileNo = mobileNo;
|
|
||||||
this.birthDate = birthDate;
|
|
||||||
this.minor = minor;
|
|
||||||
this.relation = relation;
|
|
||||||
this.gender = gender;
|
|
||||||
this.guardianName = guardianName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-569
@@ -1,569 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.OneToMany;
|
|
||||||
import jakarta.persistence.OrderBy;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import jakarta.persistence.UniqueConstraint;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A DDS Party - the customer's daily-deposit scheme account, linked to the Agent who collects for it.
|
|
||||||
* Carries the scheme's KYC, terms and two-stage overdue flags, plus a nominee list.
|
|
||||||
*
|
|
||||||
* The underlying RD (recurring deposit) account this scheme conceptually reuses is not wired up yet -
|
|
||||||
* {@code savingsAccountId} is a nullable placeholder for that future linkage (see the DDS architecture
|
|
||||||
* decision to reuse the RD engine for the deposit half). Until then the scheme/balance fields below
|
|
||||||
* are this record's own, not mirrored from any m_savings_account row.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_party", uniqueConstraints = { @UniqueConstraint(columnNames = { "party_code" }, name = "party_code_UNIQUE") })
|
|
||||||
public class Party extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
/** DDS accounts carry a flat interest rate unless the officer explicitly overrides it at creation. */
|
|
||||||
private static final BigDecimal DEFAULT_INTEREST_RATE = new BigDecimal("2");
|
|
||||||
|
|
||||||
public static final String STATUS_ACTIVE = "ACTIVE";
|
|
||||||
public static final String STATUS_CLOSED = "CLOSED";
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "client_id", nullable = false)
|
|
||||||
private Client client;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "agent_id", nullable = false)
|
|
||||||
private Agent agent;
|
|
||||||
|
|
||||||
@Column(name = "savings_account_id")
|
|
||||||
private Long savingsAccountId;
|
|
||||||
|
|
||||||
@Column(name = "sub_type", length = 60, nullable = false)
|
|
||||||
private String subType;
|
|
||||||
|
|
||||||
@Column(name = "party_code", length = 20, nullable = false)
|
|
||||||
private String partyCode;
|
|
||||||
|
|
||||||
@Column(name = "last_account", length = 50)
|
|
||||||
private String lastAccount;
|
|
||||||
|
|
||||||
@Column(name = "nominal", nullable = false)
|
|
||||||
private boolean nominal;
|
|
||||||
|
|
||||||
@Column(name = "aadhar_no", length = 20)
|
|
||||||
private String aadharNo;
|
|
||||||
|
|
||||||
@Column(name = "party_name", length = 100, nullable = false)
|
|
||||||
private String partyName;
|
|
||||||
|
|
||||||
@Column(name = "party_name1", length = 100)
|
|
||||||
private String partyName1;
|
|
||||||
|
|
||||||
@Column(name = "address1", length = 200, nullable = false)
|
|
||||||
private String address1;
|
|
||||||
|
|
||||||
@Column(name = "address2", length = 200)
|
|
||||||
private String address2;
|
|
||||||
|
|
||||||
@Column(name = "address3", length = 200)
|
|
||||||
private String address3;
|
|
||||||
|
|
||||||
@Column(name = "city", length = 100, nullable = false)
|
|
||||||
private String city;
|
|
||||||
|
|
||||||
@Column(name = "pincode", length = 20, nullable = false)
|
|
||||||
private String pincode;
|
|
||||||
|
|
||||||
@Column(name = "gender", length = 20)
|
|
||||||
private String gender;
|
|
||||||
|
|
||||||
@Column(name = "pan_card_no", length = 20)
|
|
||||||
private String panCardNo;
|
|
||||||
|
|
||||||
@Column(name = "telephone", length = 20)
|
|
||||||
private String telephone;
|
|
||||||
|
|
||||||
@Column(name = "mobile_no_sms", length = 20, nullable = false)
|
|
||||||
private String mobileNoSms;
|
|
||||||
|
|
||||||
@Column(name = "operation", length = 20, nullable = false)
|
|
||||||
private String operation;
|
|
||||||
|
|
||||||
@Column(name = "birth_date")
|
|
||||||
private LocalDate birthDate;
|
|
||||||
|
|
||||||
@Column(name = "occupation", length = 40)
|
|
||||||
private String occupation;
|
|
||||||
|
|
||||||
@Column(name = "risk", length = 20)
|
|
||||||
private String risk;
|
|
||||||
|
|
||||||
@Column(name = "ckyc_id", length = 40)
|
|
||||||
private String ckycId;
|
|
||||||
|
|
||||||
@Column(name = "adult_minor", length = 20, nullable = false)
|
|
||||||
private String adultMinor;
|
|
||||||
|
|
||||||
@Column(name = "minor_date")
|
|
||||||
private LocalDate minorDate;
|
|
||||||
|
|
||||||
@Column(name = "guardian_client_id")
|
|
||||||
private Long guardianClientId;
|
|
||||||
|
|
||||||
@Column(name = "min_account_opt", length = 40)
|
|
||||||
private String minAccountOpt;
|
|
||||||
|
|
||||||
@Column(name = "relation", length = 40)
|
|
||||||
private String relation;
|
|
||||||
|
|
||||||
@Column(name = "opening_date", nullable = false)
|
|
||||||
private LocalDate openingDate;
|
|
||||||
|
|
||||||
@Column(name = "months")
|
|
||||||
private Integer month;
|
|
||||||
|
|
||||||
@Column(name = "maturity_date")
|
|
||||||
private LocalDate maturityDate;
|
|
||||||
|
|
||||||
@Column(name = "reopen_date")
|
|
||||||
private LocalDate reopenDate;
|
|
||||||
|
|
||||||
@Column(name = "deposit_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal depositAmount;
|
|
||||||
|
|
||||||
@Column(name = "daily_deposit_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal dailyDepositAmount;
|
|
||||||
|
|
||||||
@Column(name = "days", nullable = false)
|
|
||||||
private Integer days;
|
|
||||||
|
|
||||||
@Column(name = "ledger_no", length = 40)
|
|
||||||
private String ledgerNo;
|
|
||||||
|
|
||||||
@Column(name = "maturity_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal maturityAmount;
|
|
||||||
|
|
||||||
@Column(name = "interest_rate", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal interestRate;
|
|
||||||
|
|
||||||
@Column(name = "withdraw_type", length = 20)
|
|
||||||
private String withdrawType;
|
|
||||||
|
|
||||||
@Column(name = "od1_flag", nullable = false)
|
|
||||||
private boolean od1Flag;
|
|
||||||
|
|
||||||
@Column(name = "od2_flag", nullable = false)
|
|
||||||
private boolean od2Flag;
|
|
||||||
|
|
||||||
@Column(name = "lien", nullable = false)
|
|
||||||
private boolean lien;
|
|
||||||
|
|
||||||
@Column(name = "pay_date")
|
|
||||||
private LocalDate payDate;
|
|
||||||
|
|
||||||
@Column(name = "payment_amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal paymentAmount;
|
|
||||||
|
|
||||||
@Column(name = "current_balance", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal currentBalance;
|
|
||||||
|
|
||||||
@Column(name = "opening_balance", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal openingBalance;
|
|
||||||
|
|
||||||
@Column(name = "is_sms_active", nullable = false)
|
|
||||||
private boolean isSmsActive;
|
|
||||||
|
|
||||||
@Column(name = "is_email_active", nullable = false)
|
|
||||||
private boolean isEmailActive;
|
|
||||||
|
|
||||||
@Column(name = "interest_calc", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal interestCalc;
|
|
||||||
|
|
||||||
@Column(name = "interest_date")
|
|
||||||
private LocalDate interestDate;
|
|
||||||
|
|
||||||
@Column(name = "sp_description", length = 500)
|
|
||||||
private String spDescription;
|
|
||||||
|
|
||||||
@Column(name = "freeze_code", length = 20, nullable = false)
|
|
||||||
private String freezeCode;
|
|
||||||
|
|
||||||
@Column(name = "adv_code", length = 40, nullable = false)
|
|
||||||
private String advCode;
|
|
||||||
|
|
||||||
@Column(name = "credit_to_branch", length = 100)
|
|
||||||
private String creditToBranch;
|
|
||||||
|
|
||||||
@Column(name = "credit_to_account_type", length = 100)
|
|
||||||
private String creditToAccountType;
|
|
||||||
|
|
||||||
@Column(name = "credit_to_account", length = 50)
|
|
||||||
private String creditToAccount;
|
|
||||||
|
|
||||||
@Column(name = "nominee_applicable", nullable = false)
|
|
||||||
private boolean nomineeApplicable;
|
|
||||||
|
|
||||||
@Column(name = "nominee_split_mode", length = 20)
|
|
||||||
private String nomineeSplitMode;
|
|
||||||
|
|
||||||
@Column(name = "status", length = 20, nullable = false)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Column(name = "close_date")
|
|
||||||
private LocalDate closeDate;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "party", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
|
||||||
@OrderBy("id")
|
|
||||||
private List<PartyNominee> nominees = new ArrayList<>();
|
|
||||||
|
|
||||||
protected Party() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
private Party(final Client client, final Agent agent, final String partyCode, final JsonCommand command) {
|
|
||||||
this.client = client;
|
|
||||||
this.agent = agent;
|
|
||||||
this.partyCode = partyCode;
|
|
||||||
this.subType = command.stringValueOfParameterNamed("subType");
|
|
||||||
this.lastAccount = command.stringValueOfParameterNamedAllowingNull("lastAccount");
|
|
||||||
this.nominal = command.booleanPrimitiveValueOfParameterNamed("nominal");
|
|
||||||
this.aadharNo = command.stringValueOfParameterNamedAllowingNull("aadharNo");
|
|
||||||
this.partyName = command.stringValueOfParameterNamed("partyName");
|
|
||||||
this.partyName1 = command.stringValueOfParameterNamedAllowingNull("partyName1");
|
|
||||||
this.address1 = command.stringValueOfParameterNamed("address1");
|
|
||||||
this.address2 = command.stringValueOfParameterNamedAllowingNull("address2");
|
|
||||||
this.address3 = command.stringValueOfParameterNamedAllowingNull("address3");
|
|
||||||
this.city = command.stringValueOfParameterNamed("city");
|
|
||||||
this.pincode = command.stringValueOfParameterNamed("pincode");
|
|
||||||
this.gender = command.stringValueOfParameterNamedAllowingNull("gender");
|
|
||||||
this.panCardNo = command.stringValueOfParameterNamedAllowingNull("panCardNo");
|
|
||||||
this.telephone = command.stringValueOfParameterNamedAllowingNull("telephone");
|
|
||||||
this.mobileNoSms = command.stringValueOfParameterNamed("mobileNoSms");
|
|
||||||
this.operation = command.stringValueOfParameterNamed("operation");
|
|
||||||
this.birthDate = command.localDateValueOfParameterNamed("birthDate");
|
|
||||||
this.occupation = command.stringValueOfParameterNamedAllowingNull("occupation");
|
|
||||||
this.risk = command.stringValueOfParameterNamedAllowingNull("risk");
|
|
||||||
this.ckycId = command.stringValueOfParameterNamedAllowingNull("ckycId");
|
|
||||||
this.adultMinor = command.stringValueOfParameterNamed("adultMinor");
|
|
||||||
this.minorDate = command.localDateValueOfParameterNamed("minorDate");
|
|
||||||
this.guardianClientId = command.longValueOfParameterNamed("guardianClientId");
|
|
||||||
this.minAccountOpt = command.stringValueOfParameterNamedAllowingNull("minAccountOpt");
|
|
||||||
this.relation = command.stringValueOfParameterNamedAllowingNull("relation");
|
|
||||||
this.openingDate = command.localDateValueOfParameterNamed("openingDate");
|
|
||||||
this.month = command.integerValueOfParameterNamedDefaultToNullIfZero("month");
|
|
||||||
this.maturityDate = command.localDateValueOfParameterNamed("maturityDate");
|
|
||||||
this.reopenDate = command.localDateValueOfParameterNamed("reopenDate");
|
|
||||||
this.depositAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("depositAmount"));
|
|
||||||
this.dailyDepositAmount = command.bigDecimalValueOfParameterNamed("dailyDepositAmount");
|
|
||||||
this.days = command.integerValueOfParameterNamed("days");
|
|
||||||
this.ledgerNo = command.stringValueOfParameterNamedAllowingNull("ledgerNo");
|
|
||||||
this.maturityAmount = BigDecimal.ZERO;
|
|
||||||
this.interestRate = command.parameterExists("interestRate")
|
|
||||||
? command.bigDecimalValueOfParameterNamed("interestRate")
|
|
||||||
: DEFAULT_INTEREST_RATE;
|
|
||||||
this.withdrawType = command.stringValueOfParameterNamedAllowingNull("withdrawType");
|
|
||||||
this.od1Flag = false;
|
|
||||||
this.od2Flag = false;
|
|
||||||
this.lien = command.booleanPrimitiveValueOfParameterNamed("lien");
|
|
||||||
this.payDate = command.localDateValueOfParameterNamed("payDate");
|
|
||||||
this.paymentAmount = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("paymentAmount"));
|
|
||||||
this.openingBalance = orZero(command.bigDecimalValueOfParameterNamedDefaultToNullIfZero("openingBalance"));
|
|
||||||
this.currentBalance = this.openingBalance;
|
|
||||||
this.isSmsActive = command.booleanPrimitiveValueOfParameterNamed("isSmsActive");
|
|
||||||
this.isEmailActive = command.booleanPrimitiveValueOfParameterNamed("isEmailActive");
|
|
||||||
this.interestCalc = BigDecimal.ZERO;
|
|
||||||
this.interestDate = command.localDateValueOfParameterNamed("interestDate");
|
|
||||||
this.spDescription = command.stringValueOfParameterNamedAllowingNull("spDescription");
|
|
||||||
this.freezeCode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("freezeCode"), "Normal");
|
|
||||||
this.advCode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("advCode"), "None");
|
|
||||||
this.creditToBranch = command.stringValueOfParameterNamedAllowingNull("creditToBranch");
|
|
||||||
this.creditToAccountType = command.stringValueOfParameterNamedAllowingNull("creditToAccountType");
|
|
||||||
this.creditToAccount = command.stringValueOfParameterNamedAllowingNull("creditToAccount");
|
|
||||||
this.nomineeApplicable = command.booleanPrimitiveValueOfParameterNamed("nomineeApplicable");
|
|
||||||
this.nomineeSplitMode = StringUtils.defaultIfBlank(command.stringValueOfParameterNamedAllowingNull("nomineeSplitMode"),
|
|
||||||
"Percentage");
|
|
||||||
this.status = STATUS_ACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Party fromJson(final Client client, final Agent agent, final String partyCode, final JsonCommand command) {
|
|
||||||
return new Party(client, agent, partyCode, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal orZero(final BigDecimal value) {
|
|
||||||
return value == null ? BigDecimal.ZERO : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addNominee(final PartyNominee nominee) {
|
|
||||||
nominee.setParty(this);
|
|
||||||
this.nominees.add(nominee);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetNominees() {
|
|
||||||
this.nominees.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changeClient(final Client newClient) {
|
|
||||||
this.client = newClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changeAgent(final Agent newAgent) {
|
|
||||||
this.agent = newAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Links this Party to an existing real Savings Account, resolved by account number in the write
|
|
||||||
* service. There is no unlink - once a Party's DDS scheme is tied to a real account, it stays
|
|
||||||
* tied.
|
|
||||||
*/
|
|
||||||
public void linkSavingsAccount(final Long savingsAccountId) {
|
|
||||||
this.savingsAccountId = savingsAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal credit(final BigDecimal amount) {
|
|
||||||
this.currentBalance = this.currentBalance.add(amount);
|
|
||||||
return this.currentBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal debit(final BigDecimal amount) {
|
|
||||||
this.currentBalance = this.currentBalance.subtract(amount);
|
|
||||||
return this.currentBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isClosed() {
|
|
||||||
return STATUS_CLOSED.equals(this.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this Party's account as of the given date. The balance must already be zero - a Party
|
|
||||||
* is settled to zero (a full Withdraw Deposite/payout) before it is closed, not closed with money
|
|
||||||
* still owed to it.
|
|
||||||
*/
|
|
||||||
public void close(final LocalDate closeDate) {
|
|
||||||
this.status = STATUS_CLOSED;
|
|
||||||
this.closeDate = closeDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stamps the date interest (regular or OD) was last calculated up to, so the next batch run
|
|
||||||
* accrues only the days since. Set on POST, not on calculate/preview.
|
|
||||||
*/
|
|
||||||
public void markInterestCalculatedThrough(final LocalDate date) {
|
|
||||||
this.interestDate = date;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long clientId() {
|
|
||||||
return this.client.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long agentId() {
|
|
||||||
return this.agent.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> update(final JsonCommand command) {
|
|
||||||
|
|
||||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(40);
|
|
||||||
|
|
||||||
if (command.isChangeInLongParameterNamed("clientId", this.client.getId())) {
|
|
||||||
actualChanges.put("clientId", command.longValueOfParameterNamed("clientId"));
|
|
||||||
}
|
|
||||||
if (command.isChangeInLongParameterNamed("agentId", this.agent.getId())) {
|
|
||||||
actualChanges.put("agentId", command.longValueOfParameterNamed("agentId"));
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "subType", this.subType, v -> this.subType = v);
|
|
||||||
stringChange(command, actualChanges, "lastAccount", this.lastAccount, v -> this.lastAccount = v);
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("nominal", this.nominal)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("nominal");
|
|
||||||
actualChanges.put("nominal", newValue);
|
|
||||||
this.nominal = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "aadharNo", this.aadharNo, v -> this.aadharNo = v);
|
|
||||||
stringChange(command, actualChanges, "partyName", this.partyName, v -> this.partyName = v);
|
|
||||||
stringChange(command, actualChanges, "partyName1", this.partyName1, v -> this.partyName1 = v);
|
|
||||||
stringChange(command, actualChanges, "address1", this.address1, v -> this.address1 = v);
|
|
||||||
stringChange(command, actualChanges, "address2", this.address2, v -> this.address2 = v);
|
|
||||||
stringChange(command, actualChanges, "address3", this.address3, v -> this.address3 = v);
|
|
||||||
stringChange(command, actualChanges, "city", this.city, v -> this.city = v);
|
|
||||||
stringChange(command, actualChanges, "pincode", this.pincode, v -> this.pincode = v);
|
|
||||||
stringChange(command, actualChanges, "gender", this.gender, v -> this.gender = v);
|
|
||||||
stringChange(command, actualChanges, "panCardNo", this.panCardNo, v -> this.panCardNo = v);
|
|
||||||
stringChange(command, actualChanges, "telephone", this.telephone, v -> this.telephone = v);
|
|
||||||
stringChange(command, actualChanges, "mobileNoSms", this.mobileNoSms, v -> this.mobileNoSms = v);
|
|
||||||
stringChange(command, actualChanges, "operation", this.operation, v -> this.operation = v);
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("birthDate", this.birthDate)) {
|
|
||||||
actualChanges.put("birthDate", command.stringValueOfParameterNamed("birthDate"));
|
|
||||||
this.birthDate = command.localDateValueOfParameterNamed("birthDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "occupation", this.occupation, v -> this.occupation = v);
|
|
||||||
stringChange(command, actualChanges, "risk", this.risk, v -> this.risk = v);
|
|
||||||
stringChange(command, actualChanges, "ckycId", this.ckycId, v -> this.ckycId = v);
|
|
||||||
stringChange(command, actualChanges, "adultMinor", this.adultMinor, v -> this.adultMinor = v);
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("minorDate", this.minorDate)) {
|
|
||||||
actualChanges.put("minorDate", command.stringValueOfParameterNamed("minorDate"));
|
|
||||||
this.minorDate = command.localDateValueOfParameterNamed("minorDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInLongParameterNamed("guardianClientId", this.guardianClientId)) {
|
|
||||||
final Long newValue = command.longValueOfParameterNamed("guardianClientId");
|
|
||||||
actualChanges.put("guardianClientId", newValue);
|
|
||||||
this.guardianClientId = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "minAccountOpt", this.minAccountOpt, v -> this.minAccountOpt = v);
|
|
||||||
stringChange(command, actualChanges, "relation", this.relation, v -> this.relation = v);
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("openingDate", this.openingDate)) {
|
|
||||||
actualChanges.put("openingDate", command.stringValueOfParameterNamed("openingDate"));
|
|
||||||
this.openingDate = command.localDateValueOfParameterNamed("openingDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInIntegerParameterNamed("month", this.month)) {
|
|
||||||
final Integer newValue = command.integerValueOfParameterNamed("month");
|
|
||||||
actualChanges.put("month", newValue);
|
|
||||||
this.month = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("maturityDate", this.maturityDate)) {
|
|
||||||
actualChanges.put("maturityDate", command.stringValueOfParameterNamed("maturityDate"));
|
|
||||||
this.maturityDate = command.localDateValueOfParameterNamed("maturityDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("reopenDate", this.reopenDate)) {
|
|
||||||
actualChanges.put("reopenDate", command.stringValueOfParameterNamed("reopenDate"));
|
|
||||||
this.reopenDate = command.localDateValueOfParameterNamed("reopenDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("depositAmount", this.depositAmount)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("depositAmount");
|
|
||||||
actualChanges.put("depositAmount", newValue);
|
|
||||||
this.depositAmount = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("dailyDepositAmount", this.dailyDepositAmount)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("dailyDepositAmount");
|
|
||||||
actualChanges.put("dailyDepositAmount", newValue);
|
|
||||||
this.dailyDepositAmount = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInIntegerParameterNamed("days", this.days)) {
|
|
||||||
final Integer newValue = command.integerValueOfParameterNamed("days");
|
|
||||||
actualChanges.put("days", newValue);
|
|
||||||
this.days = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("interestRate", this.interestRate)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("interestRate");
|
|
||||||
actualChanges.put("interestRate", newValue);
|
|
||||||
this.interestRate = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "ledgerNo", this.ledgerNo, v -> this.ledgerNo = v);
|
|
||||||
stringChange(command, actualChanges, "withdrawType", this.withdrawType, v -> this.withdrawType = v);
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("lien", this.lien)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("lien");
|
|
||||||
actualChanges.put("lien", newValue);
|
|
||||||
this.lien = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("payDate", this.payDate)) {
|
|
||||||
actualChanges.put("payDate", command.stringValueOfParameterNamed("payDate"));
|
|
||||||
this.payDate = command.localDateValueOfParameterNamed("payDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("paymentAmount", this.paymentAmount)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("paymentAmount");
|
|
||||||
actualChanges.put("paymentAmount", newValue);
|
|
||||||
this.paymentAmount = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBigDecimalParameterNamed("openingBalance", this.openingBalance)) {
|
|
||||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed("openingBalance");
|
|
||||||
actualChanges.put("openingBalance", newValue);
|
|
||||||
this.openingBalance = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("isSmsActive", this.isSmsActive)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("isSmsActive");
|
|
||||||
actualChanges.put("isSmsActive", newValue);
|
|
||||||
this.isSmsActive = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("isEmailActive", this.isEmailActive)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("isEmailActive");
|
|
||||||
actualChanges.put("isEmailActive", newValue);
|
|
||||||
this.isEmailActive = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChangeInDateParameterNamed("interestDate", this.interestDate)) {
|
|
||||||
actualChanges.put("interestDate", command.stringValueOfParameterNamed("interestDate"));
|
|
||||||
this.interestDate = command.localDateValueOfParameterNamed("interestDate");
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "spDescription", this.spDescription, v -> this.spDescription = v);
|
|
||||||
stringChange(command, actualChanges, "freezeCode", this.freezeCode, v -> this.freezeCode = v);
|
|
||||||
stringChange(command, actualChanges, "advCode", this.advCode, v -> this.advCode = v);
|
|
||||||
stringChange(command, actualChanges, "creditToBranch", this.creditToBranch, v -> this.creditToBranch = v);
|
|
||||||
stringChange(command, actualChanges, "creditToAccountType", this.creditToAccountType, v -> this.creditToAccountType = v);
|
|
||||||
stringChange(command, actualChanges, "creditToAccount", this.creditToAccount, v -> this.creditToAccount = v);
|
|
||||||
|
|
||||||
if (command.isChangeInBooleanParameterNamed("nomineeApplicable", this.nomineeApplicable)) {
|
|
||||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed("nomineeApplicable");
|
|
||||||
actualChanges.put("nomineeApplicable", newValue);
|
|
||||||
this.nomineeApplicable = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringChange(command, actualChanges, "nomineeSplitMode", this.nomineeSplitMode, v -> this.nomineeSplitMode = v);
|
|
||||||
|
|
||||||
return actualChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stringChange(final JsonCommand command, final Map<String, Object> actualChanges, final String paramName,
|
|
||||||
final String existingValue, final Consumer<String> setter) {
|
|
||||||
if (command.isChangeInStringParameterNamed(paramName, existingValue)) {
|
|
||||||
final String newValue = command.stringValueOfParameterNamed(paramName);
|
|
||||||
actualChanges.put(paramName, newValue);
|
|
||||||
setter.accept(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-88
@@ -1,88 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.domain;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToOne;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single nominee recorded against a {@link Party}. Maker-checker on nominee edits is enforced at
|
|
||||||
* the command layer (the whole Party update goes through the standard maker-checker command flow),
|
|
||||||
* not on this child row individually.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_party_nominee")
|
|
||||||
public class PartyNominee extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "party_id", nullable = false)
|
|
||||||
private Party party;
|
|
||||||
|
|
||||||
@Column(name = "nomination_date", nullable = false)
|
|
||||||
private LocalDate nominationDate;
|
|
||||||
|
|
||||||
@Column(name = "name", length = 100, nullable = false)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Column(name = "mobile_no", length = 20)
|
|
||||||
private String mobileNo;
|
|
||||||
|
|
||||||
@Column(name = "birth_date")
|
|
||||||
private LocalDate birthDate;
|
|
||||||
|
|
||||||
@Column(name = "minor", nullable = false)
|
|
||||||
private boolean minor;
|
|
||||||
|
|
||||||
@Column(name = "relation", length = 40, nullable = false)
|
|
||||||
private String relation;
|
|
||||||
|
|
||||||
@Column(name = "gender", length = 20, nullable = false)
|
|
||||||
private String gender;
|
|
||||||
|
|
||||||
@Column(name = "guardian_name", length = 100)
|
|
||||||
private String guardianName;
|
|
||||||
|
|
||||||
protected PartyNominee() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartyNominee(final LocalDate nominationDate, final String name, final String mobileNo, final LocalDate birthDate,
|
|
||||||
final boolean minor, final String relation, final String gender, final String guardianName) {
|
|
||||||
this.nominationDate = nominationDate;
|
|
||||||
this.name = name;
|
|
||||||
this.mobileNo = StringUtils.defaultIfEmpty(mobileNo, null);
|
|
||||||
this.birthDate = birthDate;
|
|
||||||
this.minor = minor;
|
|
||||||
this.relation = relation;
|
|
||||||
this.gender = gender;
|
|
||||||
this.guardianName = StringUtils.defaultIfEmpty(guardianName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setParty(final Party party) {
|
|
||||||
this.party = party;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a Close Party command, or a Party Tran collection, targets a Party that is already
|
|
||||||
* closed.
|
|
||||||
*/
|
|
||||||
public class PartyAlreadyClosedException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public PartyAlreadyClosedException(final String partyCode) {
|
|
||||||
super("error.msg.party.already.closed", "Party " + partyCode + " is already closed", partyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a Close Party command is attempted while the Party's balance is still non-zero - the
|
|
||||||
* remaining balance must be paid out (a Withdraw Deposite to zero) before the account can be closed.
|
|
||||||
*/
|
|
||||||
public class PartyHasBalanceException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public PartyHasBalanceException(final String partyCode) {
|
|
||||||
super("error.msg.party.has.balance", "Party " + partyCode + " still has a non-zero balance and cannot be closed", partyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RuntimeException} thrown when a DDS party resource is not found.
|
|
||||||
*/
|
|
||||||
public class PartyNotFoundException extends AbstractPlatformResourceNotFoundException {
|
|
||||||
|
|
||||||
public PartyNotFoundException(final Long id) {
|
|
||||||
super("error.msg.party.id.invalid", "Party with identifier " + id + " does not exist", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartyNotFoundException(final Long id, final EmptyResultDataAccessException e) {
|
|
||||||
super("error.msg.party.id.invalid", "Party with identifier " + id + " does not exist", id, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartyNotFoundException(final String partyCode) {
|
|
||||||
super("error.msg.party.code.invalid", "Party with code " + partyCode + " does not exist", partyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when the savingsAccountNo given for a Party belongs to a different client than the Party's
|
|
||||||
* own clientId - a Party can only be linked to its own client's savings account.
|
|
||||||
*/
|
|
||||||
public class PartySavingsAccountClientMismatchException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public PartySavingsAccountClientMismatchException(final String savingsAccountNo) {
|
|
||||||
super("error.msg.party.savings.account.client.mismatch",
|
|
||||||
"Savings account " + savingsAccountNo + " does not belong to this party's client", savingsAccountNo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a Party's savingsAccountNo does not match any non-closed Savings Account.
|
|
||||||
*/
|
|
||||||
public class PartySavingsAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
|
|
||||||
|
|
||||||
public PartySavingsAccountNotFoundException(final String savingsAccountNo) {
|
|
||||||
super("error.msg.party.savings.account.not.found", "No open savings account found with account number " + savingsAccountNo,
|
|
||||||
savingsAccountNo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.report.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic tabular result shape shared by every DDS report - one column list plus one row list, both
|
|
||||||
* keyed the same way, so a single report viewer screen can render any of them without a bespoke
|
|
||||||
* result type per report.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Builder
|
|
||||||
public final class DdsReportData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final String reportKey;
|
|
||||||
private final boolean available;
|
|
||||||
private final String message;
|
|
||||||
private final List<String> columns;
|
|
||||||
private final List<Map<String, Object>> rows;
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.data;
|
|
||||||
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Builder
|
|
||||||
public final class PartyTransactionData implements Serializable {
|
|
||||||
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final Long id;
|
|
||||||
private final Long partyId;
|
|
||||||
private final String partyCode;
|
|
||||||
private final String partyName;
|
|
||||||
private final Long agentId;
|
|
||||||
private final String agentCode;
|
|
||||||
private final LocalDate transactionDate;
|
|
||||||
private final String transactionType;
|
|
||||||
private final String direction;
|
|
||||||
private final String transactionMode;
|
|
||||||
private final String accountType;
|
|
||||||
private final String accountNo;
|
|
||||||
private final BigDecimal amount;
|
|
||||||
private final String chequeNo;
|
|
||||||
private final String tokenNo;
|
|
||||||
private final String description;
|
|
||||||
private final BigDecimal balanceAfterTransaction;
|
|
||||||
}
|
|
||||||
-110
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.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 lombok.Getter;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single posted entry on the DDS ledger: a Party's day's collection ("Party Tran", direction
|
|
||||||
* CREDIT), one leg of a "Withdraw Deposite" transfer between two parties (the creditor leg CREDIT,
|
|
||||||
* the debtor leg DEBIT), or an Agent's settlement of collected cash to its branch ("Agent Tran",
|
|
||||||
* direction DEBIT - no {@link Party} involved, so {@code party} is null for this type).
|
|
||||||
* {@code balanceAfterTransaction} holds the Party's balance for Party-linked rows, or the Agent's
|
|
||||||
* balance for an Agent Tran row. Immutable once posted - like any transaction, it is reversed with
|
|
||||||
* an opposing entry, not edited, so there is deliberately no update API for this entity.
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "m_dds_party_transaction")
|
|
||||||
public class PartyTransaction extends AbstractPersistableCustom<Long> {
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "party_id")
|
|
||||||
private Party party;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "agent_id", nullable = false)
|
|
||||||
private Agent agent;
|
|
||||||
|
|
||||||
@Column(name = "transaction_date", nullable = false)
|
|
||||||
private LocalDate transactionDate;
|
|
||||||
|
|
||||||
@Column(name = "transaction_type", length = 30, nullable = false)
|
|
||||||
private String transactionType;
|
|
||||||
|
|
||||||
@Column(name = "direction", length = 10, nullable = false)
|
|
||||||
private String direction;
|
|
||||||
|
|
||||||
@Column(name = "transaction_mode", length = 20)
|
|
||||||
private String transactionMode;
|
|
||||||
|
|
||||||
@Column(name = "account_type", length = 100)
|
|
||||||
private String accountType;
|
|
||||||
|
|
||||||
@Column(name = "account_no", length = 50)
|
|
||||||
private String accountNo;
|
|
||||||
|
|
||||||
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal amount;
|
|
||||||
|
|
||||||
@Column(name = "cheque_no", length = 40)
|
|
||||||
private String chequeNo;
|
|
||||||
|
|
||||||
@Column(name = "token_no", length = 40)
|
|
||||||
private String tokenNo;
|
|
||||||
|
|
||||||
@Column(name = "description", length = 500)
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Column(name = "balance_after_transaction", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal balanceAfterTransaction;
|
|
||||||
|
|
||||||
protected PartyTransaction() {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartyTransaction(final Party party, final Agent agent, final LocalDate transactionDate, final String transactionType,
|
|
||||||
final String direction, final String transactionMode, final String accountType, final String accountNo,
|
|
||||||
final BigDecimal amount, final String chequeNo, final String tokenNo, final String description,
|
|
||||||
final BigDecimal balanceAfterTransaction) {
|
|
||||||
this.party = party;
|
|
||||||
this.agent = agent;
|
|
||||||
this.transactionDate = transactionDate;
|
|
||||||
this.transactionType = transactionType;
|
|
||||||
this.direction = direction;
|
|
||||||
this.transactionMode = transactionMode;
|
|
||||||
this.accountType = accountType;
|
|
||||||
this.accountNo = accountNo;
|
|
||||||
this.amount = amount;
|
|
||||||
this.chequeNo = chequeNo;
|
|
||||||
this.tokenNo = tokenNo;
|
|
||||||
this.description = description;
|
|
||||||
this.balanceAfterTransaction = balanceAfterTransaction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when an Agent Tran settlement amount exceeds the Agent's current (collected but not yet
|
|
||||||
* settled) balance.
|
|
||||||
*/
|
|
||||||
public class InsufficientAgentBalanceException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public InsufficientAgentBalanceException(final String agentCode) {
|
|
||||||
super("error.msg.agent.insufficient.balance",
|
|
||||||
"Agent " + agentCode + " does not have sufficient collected balance for this settlement", agentCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when a Withdraw Deposite transfer would take the debtor Party's balance below zero.
|
|
||||||
*/
|
|
||||||
public class InsufficientPartyBalanceException extends AbstractPlatformDomainRuleException {
|
|
||||||
|
|
||||||
public InsufficientPartyBalanceException(final String partyCode) {
|
|
||||||
super("error.msg.party.insufficient.balance", "Party " + partyCode + " does not have sufficient balance for this transfer",
|
|
||||||
partyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.exception;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RuntimeException} thrown when a DDS party transaction resource is not found.
|
|
||||||
*/
|
|
||||||
public class PartyTransactionNotFoundException extends AbstractPlatformResourceNotFoundException {
|
|
||||||
|
|
||||||
public PartyTransactionNotFoundException(final Long id, final EmptyResultDataAccessException e) {
|
|
||||||
super("error.msg.partytransaction.id.invalid", "Party transaction with identifier " + id + " does not exist", id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+7
-1
@@ -31,7 +31,8 @@ public enum DepositAccountOnClosureType {
|
|||||||
WITHDRAW_DEPOSIT(100, "depositAccountClosureType.withdrawDeposit"), //
|
WITHDRAW_DEPOSIT(100, "depositAccountClosureType.withdrawDeposit"), //
|
||||||
TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), //
|
TRANSFER_TO_SAVINGS(200, "depositAccountClosureType.transferToSavings"), //
|
||||||
REINVEST_PRINCIPAL_AND_INTEREST(300, "depositAccountClosureType.reinvestPrincipalAndInterest"), //
|
REINVEST_PRINCIPAL_AND_INTEREST(300, "depositAccountClosureType.reinvestPrincipalAndInterest"), //
|
||||||
REINVEST_PRINCIPAL_ONLY(400, "depositAccountClosureType.reinvestPrincipalOnly"); //
|
REINVEST_PRINCIPAL_ONLY(400, "depositAccountClosureType.reinvestPrincipalOnly"), //
|
||||||
|
PARTIAL_REINVEST(500, "depositAccountClosureType.partialReinvest"); //
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
@@ -51,6 +52,7 @@ public enum DepositAccountOnClosureType {
|
|||||||
case 200 -> TRANSFER_TO_SAVINGS;
|
case 200 -> TRANSFER_TO_SAVINGS;
|
||||||
case 300 -> REINVEST_PRINCIPAL_AND_INTEREST;
|
case 300 -> REINVEST_PRINCIPAL_AND_INTEREST;
|
||||||
case 400 -> REINVEST_PRINCIPAL_ONLY;
|
case 400 -> REINVEST_PRINCIPAL_ONLY;
|
||||||
|
case 500 -> PARTIAL_REINVEST;
|
||||||
default -> INVALID;
|
default -> INVALID;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -85,6 +87,10 @@ public enum DepositAccountOnClosureType {
|
|||||||
return this.equals(INVALID);
|
return this.equals(INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPartialReinvest() {
|
||||||
|
return this.equals(PARTIAL_REINVEST);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: do we really need this?!?
|
// TODO: do we really need this?!?
|
||||||
public static Object[] integerValues() {
|
public static Object[] integerValues() {
|
||||||
return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray();
|
return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray();
|
||||||
|
|||||||
+1
@@ -208,6 +208,7 @@ public final class DepositsApiConstants {
|
|||||||
public static final String onAccountClosureIdParamName = "onAccountClosureId";
|
public static final String onAccountClosureIdParamName = "onAccountClosureId";
|
||||||
public static final String transferDescriptionParamName = "transferDescription";
|
public static final String transferDescriptionParamName = "transferDescription";
|
||||||
public static final String toSavingsAccountIdParamName = "toSavingsAccountId";
|
public static final String toSavingsAccountIdParamName = "toSavingsAccountId";
|
||||||
|
public static final String reinvestAmountParamName = "reinvestAmount";
|
||||||
public static final String savingsAccounts = "savingsAccounts";
|
public static final String savingsAccounts = "savingsAccounts";
|
||||||
public static final String maturityInstructionIdParamName = "maturityInstructionId";
|
public static final String maturityInstructionIdParamName = "maturityInstructionId";
|
||||||
public static final String transferToSavingsIdParamName = "transferToSavingsId";
|
public static final String transferToSavingsIdParamName = "transferToSavingsId";
|
||||||
|
|||||||
-1
@@ -275,7 +275,6 @@ public class SavingsApiConstants {
|
|||||||
// Additional custom fields
|
// Additional custom fields
|
||||||
public static final String balanceFrequencyParamName = "balanceFrequency";
|
public static final String balanceFrequencyParamName = "balanceFrequency";
|
||||||
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
|
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
|
||||||
public static final String productCategoryParamName = "productCategory";
|
|
||||||
|
|
||||||
// Current Product — new UI fields
|
// Current Product — new UI fields
|
||||||
public static final String balanceSwapOptionParamName = "balanceSwapOption";
|
public static final String balanceSwapOptionParamName = "balanceSwapOption";
|
||||||
|
|||||||
-1
@@ -144,7 +144,6 @@ public final class SavingsProductData implements Serializable {
|
|||||||
@Getter @Setter private Boolean enableLockinPeriod;
|
@Getter @Setter private Boolean enableLockinPeriod;
|
||||||
@Getter @Setter private String balanceFrequency;
|
@Getter @Setter private String balanceFrequency;
|
||||||
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
|
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
|
||||||
@Getter @Setter private String productCategory;
|
|
||||||
|
|
||||||
// New current product fields
|
// New current product fields
|
||||||
@Getter @Setter private Boolean balanceSwapOption;
|
@Getter @Setter private Boolean balanceSwapOption;
|
||||||
|
|||||||
+4
@@ -797,6 +797,10 @@ public final class SavingsEnumerations {
|
|||||||
optionData = new EnumOptionData(DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getValue().longValue(),
|
optionData = new EnumOptionData(DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getValue().longValue(),
|
||||||
DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getCode(), "Re-Invest Principal Only");
|
DepositAccountOnClosureType.REINVEST_PRINCIPAL_ONLY.getCode(), "Re-Invest Principal Only");
|
||||||
break;
|
break;
|
||||||
|
case PARTIAL_REINVEST:
|
||||||
|
optionData = new EnumOptionData(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue().longValue(),
|
||||||
|
DepositAccountOnClosureType.PARTIAL_REINVEST.getCode(), "Partial Re-Invest");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return optionData;
|
return optionData;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -1263,6 +1263,11 @@ public class AccountingProcessorHelper {
|
|||||||
accountMapping = paymentChannelSpecificAccountMapping;
|
accountMapping = paymentChannelSpecificAccountMapping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accountMapping == null) {
|
||||||
|
throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.SAVING, savingsProductId,
|
||||||
|
CashAccountsForSavings.fromInt(accountMappingTypeId).toString());
|
||||||
|
}
|
||||||
glAccount = accountMapping.getGlAccount();
|
glAccount = accountMapping.getGlAccount();
|
||||||
}
|
}
|
||||||
return glAccount;
|
return glAccount;
|
||||||
|
|||||||
-104
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.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.GET;
|
|
||||||
import jakarta.ws.rs.POST;
|
|
||||||
import jakarta.ws.rs.PUT;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.PathParam;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/agents")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Agents", description = "A DDS collection agent - either a person or, at the top of the hierarchy, the bank itself - with its own settlement savings account and guarantors.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AgentsApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "AGENT";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final AgentReadPlatformService readPlatformService;
|
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve DDS Agents", description = "Returns the list of DDS agents.")
|
|
||||||
public Collection<AgentData> retrieveAll() {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{agentId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve a DDS Agent", description = "Returns the details of a DDS agent, including its guarantors.")
|
|
||||||
public AgentData retrieveOne(@PathParam("agentId") @Parameter(description = "agentId") final Long agentId) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveOne(agentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Create a DDS Agent", description = "Mandatory fields: clientId, agentNameId, agentAddress1, agentCity, "
|
|
||||||
+ "agentPinCode, agentSavingAccountType, agentSavingAccountNo, agentOpeningDate, agentBirthDate, homeBranch, "
|
|
||||||
+ "savingBranch, tdsApplicable, commissionCalculation\n" + "\n"
|
|
||||||
+ "Optional fields: agentAddress2, agentTelephone, agentOpeningBalance, tdsPercent, transferBranch, "
|
|
||||||
+ "transferAmountAccountType, transferAccountNo, limit, machineRent, agentSecurityBranch, agentSecurityAccountType, "
|
|
||||||
+ "agentSecurityAccountNo, otherCommission, agentDeposit, festivalAdvance, otherDeduction, limits, guarantors")
|
|
||||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.createAgent() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PUT
|
|
||||||
@Path("{agentId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Update a DDS Agent", description = "Updates the details of a DDS agent.")
|
|
||||||
public CommandProcessingResult update(@PathParam("agentId") @Parameter(description = "agentId") final Long agentId,
|
|
||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.updateAgent(agentId) //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.domain;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface AgentRepository extends JpaRepository<Agent, Long> {
|
|
||||||
|
|
||||||
@Query("select coalesce(max(cast(substring(a.agentCode, 3) as long)), 0) from Agent a where a.agentCode like 'AG%'")
|
|
||||||
Long retrieveMaxAgentCodeSuffix();
|
|
||||||
|
|
||||||
Optional<Agent> findByAgentCode(String agentCode);
|
|
||||||
|
|
||||||
@Query("select a from Agent a where a.id between :agentFrom and :agentTo")
|
|
||||||
List<Agent> findBetweenIds(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo);
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "AGENT", action = "CREATE")
|
|
||||||
public class CreateAgentCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final AgentWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CreateAgentCommandHandler(final AgentWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.createAgent(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "AGENT", action = "UPDATE")
|
|
||||||
public class UpdateAgentCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final AgentWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public UpdateAgentCommandHandler(final AgentWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.updateAgent(command.entityId(), command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-409
@@ -1,409 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.serialization;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates create/update commands for an Agent - the DDS collection agent. As of 2026-08-24, Agent
|
|
||||||
* does the daily collection work directly (the SubAgent tier was removed), so this deserializer carries
|
|
||||||
* the collection/commission/limit/security-account fields SubAgent used to own, alongside Agent's
|
|
||||||
* original identity/settlement-account fields.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public final class AgentCommandFromApiJsonDeserializer {
|
|
||||||
|
|
||||||
public static final String CLIENT_ID = "clientId";
|
|
||||||
public static final String AGENT_NAME_ID = "agentNameId";
|
|
||||||
public static final String AGENT_ADDRESS1 = "agentAddress1";
|
|
||||||
public static final String AGENT_ADDRESS2 = "agentAddress2";
|
|
||||||
public static final String AGENT_CITY = "agentCity";
|
|
||||||
public static final String AGENT_PINCODE = "agentPinCode";
|
|
||||||
public static final String AGENT_TELEPHONE = "agentTelephone";
|
|
||||||
public static final String AGENT_SAVING_ACCOUNT_TYPE = "agentSavingAccountType";
|
|
||||||
public static final String AGENT_SAVING_ACCOUNT_NO = "agentSavingAccountNo";
|
|
||||||
public static final String AGENT_OPENING_DATE = "agentOpeningDate";
|
|
||||||
public static final String AGENT_BIRTH_DATE = "agentBirthDate";
|
|
||||||
public static final String AGENT_OPENING_BALANCE = "agentOpeningBalance";
|
|
||||||
public static final String HOME_BRANCH = "homeBranch";
|
|
||||||
public static final String SAVING_BRANCH = "savingBranch";
|
|
||||||
public static final String TDS_APPLICABLE = "tdsApplicable";
|
|
||||||
public static final String TDS_PERCENT = "tdsPercent";
|
|
||||||
public static final String TRANSFER_BRANCH = "transferBranch";
|
|
||||||
public static final String TRANSFER_AMOUNT_ACCOUNT_TYPE = "transferAmountAccountType";
|
|
||||||
public static final String TRANSFER_ACCOUNT_NO = "transferAccountNo";
|
|
||||||
public static final String COMMISSION_CALCULATION = "commissionCalculation";
|
|
||||||
public static final String LIMIT = "limit";
|
|
||||||
public static final String MACHINE_RENT = "machineRent";
|
|
||||||
public static final String AGENT_SECURITY_BRANCH = "agentSecurityBranch";
|
|
||||||
public static final String AGENT_SECURITY_ACCOUNT_TYPE = "agentSecurityAccountType";
|
|
||||||
public static final String AGENT_SECURITY_ACCOUNT_NO = "agentSecurityAccountNo";
|
|
||||||
public static final String OTHER_COMMISSION = "otherCommission";
|
|
||||||
public static final String AGENT_DEPOSIT = "agentDeposit";
|
|
||||||
public static final String FESTIVAL_ADVANCE = "festivalAdvance";
|
|
||||||
public static final String OTHER_DEDUCTION = "otherDeduction";
|
|
||||||
public static final String LIMITS = "limits";
|
|
||||||
public static final String GUARANTORS = "guarantors";
|
|
||||||
public static final String DATE_FORMAT = "dateFormat";
|
|
||||||
public static final String LOCALE = "locale";
|
|
||||||
|
|
||||||
private static final String GUARANTOR_CLIENT_ID = "clientId";
|
|
||||||
private static final String GUARANTOR_NAME = "guarantorName";
|
|
||||||
private static final String GUARANTOR_ADDRESS1 = "address1";
|
|
||||||
private static final String GUARANTOR_ADDRESS2 = "address2";
|
|
||||||
private static final String GUARANTOR_CITY = "city";
|
|
||||||
private static final String GUARANTOR_PINCODE = "pinCode";
|
|
||||||
private static final String GUARANTOR_TELEPHONE = "telephone";
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList(CLIENT_ID, AGENT_NAME_ID, AGENT_ADDRESS1,
|
|
||||||
AGENT_ADDRESS2, AGENT_CITY, AGENT_PINCODE, AGENT_TELEPHONE, AGENT_SAVING_ACCOUNT_TYPE, AGENT_SAVING_ACCOUNT_NO,
|
|
||||||
AGENT_OPENING_DATE, AGENT_BIRTH_DATE, AGENT_OPENING_BALANCE, HOME_BRANCH, SAVING_BRANCH, TDS_APPLICABLE, TDS_PERCENT,
|
|
||||||
TRANSFER_BRANCH, TRANSFER_AMOUNT_ACCOUNT_TYPE, TRANSFER_ACCOUNT_NO, COMMISSION_CALCULATION, LIMIT, MACHINE_RENT,
|
|
||||||
AGENT_SECURITY_BRANCH, AGENT_SECURITY_ACCOUNT_TYPE, AGENT_SECURITY_ACCOUNT_NO, OTHER_COMMISSION, AGENT_DEPOSIT,
|
|
||||||
FESTIVAL_ADVANCE, OTHER_DEDUCTION, LIMITS, GUARANTORS, DATE_FORMAT, LOCALE));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_LIMIT_PARAMETERS = new HashSet<>(Arrays.asList("fromDate", "toDate", "limit"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_GUARANTOR_PARAMETERS = new HashSet<>(Arrays.asList(GUARANTOR_CLIENT_ID, GUARANTOR_NAME,
|
|
||||||
GUARANTOR_ADDRESS1, GUARANTOR_ADDRESS2, GUARANTOR_CITY, GUARANTOR_PINCODE, GUARANTOR_TELEPHONE));
|
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public AgentCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForCreate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
final Long clientId = this.fromApiJsonHelper.extractLongNamed(CLIENT_ID, element);
|
|
||||||
baseDataValidator.reset().parameter(CLIENT_ID).value(clientId).notNull().integerGreaterThanZero();
|
|
||||||
|
|
||||||
final String agentName = this.fromApiJsonHelper.extractStringNamed(AGENT_NAME_ID, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_NAME_ID).value(agentName).notBlank().notExceedingLengthOf(100);
|
|
||||||
|
|
||||||
final String agentAddress1 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS1, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_ADDRESS1).value(agentAddress1).notBlank().notExceedingLengthOf(200);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS2, element)) {
|
|
||||||
final String agentAddress2 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS2, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_ADDRESS2).value(agentAddress2).ignoreIfNull().notExceedingLengthOf(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String agentCity = this.fromApiJsonHelper.extractStringNamed(AGENT_CITY, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_CITY).value(agentCity).notBlank().notExceedingLengthOf(100);
|
|
||||||
|
|
||||||
final String agentPinCode = this.fromApiJsonHelper.extractStringNamed(AGENT_PINCODE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_PINCODE).value(agentPinCode).notBlank().notExceedingLengthOf(20);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_TELEPHONE, element)) {
|
|
||||||
final String agentTelephone = this.fromApiJsonHelper.extractStringNamed(AGENT_TELEPHONE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_TELEPHONE).value(agentTelephone).ignoreIfNull().notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String agentSavingAccountType = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_TYPE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_TYPE).value(agentSavingAccountType).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
|
|
||||||
final String agentSavingAccountNo = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_NO, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_NO).value(agentSavingAccountNo).notBlank().notExceedingLengthOf(50);
|
|
||||||
|
|
||||||
final var agentOpeningDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_OPENING_DATE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_OPENING_DATE).value(agentOpeningDate).notNull();
|
|
||||||
|
|
||||||
final var agentBirthDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_BIRTH_DATE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_BIRTH_DATE).value(agentBirthDate).notNull();
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_BALANCE, element)) {
|
|
||||||
final var agentOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_OPENING_BALANCE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_OPENING_BALANCE).value(agentOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String homeBranch = this.fromApiJsonHelper.extractStringNamed(HOME_BRANCH, element);
|
|
||||||
baseDataValidator.reset().parameter(HOME_BRANCH).value(homeBranch).notBlank().notExceedingLengthOf(100);
|
|
||||||
|
|
||||||
final String savingBranch = this.fromApiJsonHelper.extractStringNamed(SAVING_BRANCH, element);
|
|
||||||
baseDataValidator.reset().parameter(SAVING_BRANCH).value(savingBranch).notBlank().notExceedingLengthOf(100);
|
|
||||||
|
|
||||||
baseDataValidator.reset().parameter(TDS_APPLICABLE).trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(TDS_APPLICABLE,
|
|
||||||
element));
|
|
||||||
baseDataValidator.reset().parameter(COMMISSION_CALCULATION)
|
|
||||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(COMMISSION_CALCULATION, element));
|
|
||||||
|
|
||||||
validateOptionalStrings(element, baseDataValidator);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(LIMITS, element)) {
|
|
||||||
validateLimits(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(GUARANTORS, element)) {
|
|
||||||
validateGuarantors(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForUpdate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(CLIENT_ID, element)) {
|
|
||||||
final Long clientId = this.fromApiJsonHelper.extractLongNamed(CLIENT_ID, element);
|
|
||||||
baseDataValidator.reset().parameter(CLIENT_ID).value(clientId).notNull().integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_NAME_ID, element)) {
|
|
||||||
final String agentName = this.fromApiJsonHelper.extractStringNamed(AGENT_NAME_ID, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_NAME_ID).value(agentName).notBlank().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS1, element)) {
|
|
||||||
final String agentAddress1 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS1, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_ADDRESS1).value(agentAddress1).notBlank().notExceedingLengthOf(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_ADDRESS2, element)) {
|
|
||||||
final String agentAddress2 = this.fromApiJsonHelper.extractStringNamed(AGENT_ADDRESS2, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_ADDRESS2).value(agentAddress2).ignoreIfNull().notExceedingLengthOf(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_CITY, element)) {
|
|
||||||
final String agentCity = this.fromApiJsonHelper.extractStringNamed(AGENT_CITY, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_CITY).value(agentCity).notBlank().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_PINCODE, element)) {
|
|
||||||
final String agentPinCode = this.fromApiJsonHelper.extractStringNamed(AGENT_PINCODE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_PINCODE).value(agentPinCode).notBlank().notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_TELEPHONE, element)) {
|
|
||||||
final String agentTelephone = this.fromApiJsonHelper.extractStringNamed(AGENT_TELEPHONE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_TELEPHONE).value(agentTelephone).ignoreIfNull().notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SAVING_ACCOUNT_TYPE, element)) {
|
|
||||||
final String agentSavingAccountType = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_TYPE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_TYPE).value(agentSavingAccountType).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SAVING_ACCOUNT_NO, element)) {
|
|
||||||
final String agentSavingAccountNo = this.fromApiJsonHelper.extractStringNamed(AGENT_SAVING_ACCOUNT_NO, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_SAVING_ACCOUNT_NO).value(agentSavingAccountNo).notBlank().notExceedingLengthOf(50);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_DATE, element)) {
|
|
||||||
final var agentOpeningDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_OPENING_DATE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_OPENING_DATE).value(agentOpeningDate).notNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_BIRTH_DATE, element)) {
|
|
||||||
final var agentBirthDate = this.fromApiJsonHelper.extractLocalDateNamed(AGENT_BIRTH_DATE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_BIRTH_DATE).value(agentBirthDate).notNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_OPENING_BALANCE, element)) {
|
|
||||||
final var agentOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_OPENING_BALANCE, element);
|
|
||||||
baseDataValidator.reset().parameter(AGENT_OPENING_BALANCE).value(agentOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(HOME_BRANCH, element)) {
|
|
||||||
final String homeBranch = this.fromApiJsonHelper.extractStringNamed(HOME_BRANCH, element);
|
|
||||||
baseDataValidator.reset().parameter(HOME_BRANCH).value(homeBranch).notBlank().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(SAVING_BRANCH, element)) {
|
|
||||||
final String savingBranch = this.fromApiJsonHelper.extractStringNamed(SAVING_BRANCH, element);
|
|
||||||
baseDataValidator.reset().parameter(SAVING_BRANCH).value(savingBranch).notBlank().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(TDS_APPLICABLE, element)) {
|
|
||||||
baseDataValidator.reset().parameter(TDS_APPLICABLE)
|
|
||||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(TDS_APPLICABLE, element));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(COMMISSION_CALCULATION, element)) {
|
|
||||||
baseDataValidator.reset().parameter(COMMISSION_CALCULATION)
|
|
||||||
.trueOrFalseRequired(this.fromApiJsonHelper.extractStringNamed(COMMISSION_CALCULATION, element));
|
|
||||||
}
|
|
||||||
|
|
||||||
validateOptionalStrings(element, baseDataValidator);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(LIMITS, element)) {
|
|
||||||
validateLimits(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(GUARANTORS, element)) {
|
|
||||||
validateGuarantors(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateOptionalStrings(final JsonElement element, final DataValidatorBuilder v) {
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(TDS_PERCENT, element)) {
|
|
||||||
v.reset().parameter(TDS_PERCENT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(TDS_PERCENT, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_BRANCH, element)) {
|
|
||||||
v.reset().parameter(TRANSFER_BRANCH).value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_BRANCH, element))
|
|
||||||
.ignoreIfNull().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_AMOUNT_ACCOUNT_TYPE, element)) {
|
|
||||||
v.reset().parameter(TRANSFER_AMOUNT_ACCOUNT_TYPE)
|
|
||||||
.value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_AMOUNT_ACCOUNT_TYPE, element)).ignoreIfNull()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(TRANSFER_ACCOUNT_NO, element)) {
|
|
||||||
v.reset().parameter(TRANSFER_ACCOUNT_NO).value(this.fromApiJsonHelper.extractStringNamed(TRANSFER_ACCOUNT_NO, element))
|
|
||||||
.ignoreIfNull().notExceedingLengthOf(50);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(LIMIT, element)) {
|
|
||||||
v.reset().parameter(LIMIT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LIMIT, element)).ignoreIfNull()
|
|
||||||
.zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(MACHINE_RENT, element)) {
|
|
||||||
v.reset().parameter(MACHINE_RENT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(MACHINE_RENT, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_BRANCH, element)) {
|
|
||||||
v.reset().parameter(AGENT_SECURITY_BRANCH).value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_BRANCH, element))
|
|
||||||
.ignoreIfNull().notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_ACCOUNT_TYPE, element)) {
|
|
||||||
v.reset().parameter(AGENT_SECURITY_ACCOUNT_TYPE)
|
|
||||||
.value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_ACCOUNT_TYPE, element)).ignoreIfNull()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_SECURITY_ACCOUNT_NO, element)) {
|
|
||||||
v.reset().parameter(AGENT_SECURITY_ACCOUNT_NO)
|
|
||||||
.value(this.fromApiJsonHelper.extractStringNamed(AGENT_SECURITY_ACCOUNT_NO, element)).ignoreIfNull()
|
|
||||||
.notExceedingLengthOf(50);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(OTHER_COMMISSION, element)) {
|
|
||||||
v.reset().parameter(OTHER_COMMISSION).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(OTHER_COMMISSION, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(AGENT_DEPOSIT, element)) {
|
|
||||||
v.reset().parameter(AGENT_DEPOSIT).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(AGENT_DEPOSIT, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(FESTIVAL_ADVANCE, element)) {
|
|
||||||
v.reset().parameter(FESTIVAL_ADVANCE).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(FESTIVAL_ADVANCE, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(OTHER_DEDUCTION, element)) {
|
|
||||||
v.reset().parameter(OTHER_DEDUCTION).value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(OTHER_DEDUCTION, element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateLimits(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("agent.limit");
|
|
||||||
final var limitsArray = topLevelJson.getAsJsonArray(LIMITS);
|
|
||||||
for (int i = 0; i < limitsArray.size(); i++) {
|
|
||||||
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(limitJson, SUPPORTED_LIMIT_PARAMETERS);
|
|
||||||
|
|
||||||
v.reset().parameterAtIndexArray("fromDate", i).value(extractString(limitJson, "fromDate")).notBlank();
|
|
||||||
v.reset().parameterAtIndexArray("toDate", i).value(extractString(limitJson, "toDate")).notBlank();
|
|
||||||
v.reset().parameterAtIndexArray("limit", i).value(extractBigDecimal(limitJson, "limit")).notNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateGuarantors(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
final DataValidatorBuilder guarantorValidator = new DataValidatorBuilder(dataValidationErrors).resource("agent.guarantor");
|
|
||||||
|
|
||||||
final var guarantorsArray = topLevelJson.getAsJsonArray(GUARANTORS);
|
|
||||||
for (int i = 0; i < guarantorsArray.size(); i++) {
|
|
||||||
final JsonObject guarantorJson = guarantorsArray.get(i).getAsJsonObject();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(guarantorJson, SUPPORTED_GUARANTOR_PARAMETERS);
|
|
||||||
|
|
||||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_CLIENT_ID, i)
|
|
||||||
.value(extractLong(guarantorJson, GUARANTOR_CLIENT_ID)).notNull().integerGreaterThanZero();
|
|
||||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_NAME, i).value(extractString(guarantorJson, GUARANTOR_NAME))
|
|
||||||
.notBlank().notExceedingLengthOf(100);
|
|
||||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_ADDRESS1, i).value(extractString(guarantorJson, GUARANTOR_ADDRESS1))
|
|
||||||
.notBlank().notExceedingLengthOf(200);
|
|
||||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_CITY, i).value(extractString(guarantorJson, GUARANTOR_CITY))
|
|
||||||
.notBlank().notExceedingLengthOf(100);
|
|
||||||
guarantorValidator.reset().parameterAtIndexArray(GUARANTOR_PINCODE, i).value(extractString(guarantorJson, GUARANTOR_PINCODE))
|
|
||||||
.notBlank().notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractString(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long extractLong(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsLong() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private java.math.BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
|
||||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
|
||||||
dataValidationErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
|
||||||
|
|
||||||
public interface AgentReadPlatformService {
|
|
||||||
|
|
||||||
Collection<AgentData> retrieveAll();
|
|
||||||
|
|
||||||
AgentData retrieveOne(Long agentId);
|
|
||||||
}
|
|
||||||
-176
@@ -1,176 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.service;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
|
||||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
|
||||||
import org.apache.fineract.organisation.agent.data.AgentGuarantorData;
|
|
||||||
import org.apache.fineract.organisation.agent.data.AgentLimitData;
|
|
||||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AgentReadPlatformServiceImpl implements AgentReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
private static final AgentMapper AGENT_MAPPER = new AgentMapper();
|
|
||||||
private static final AgentLimitMapper LIMIT_MAPPER = new AgentLimitMapper();
|
|
||||||
private static final AgentGuarantorMapper GUARANTOR_MAPPER = new AgentGuarantorMapper();
|
|
||||||
|
|
||||||
private static final class AgentMapper implements RowMapper<AgentData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " a.id as id, a.client_id as clientId, c.display_name as clientName, a.agent_code as agentCode, "
|
|
||||||
+ " a.agent_name as agentNameId, a.agent_address1 as agentAddress1, a.agent_address2 as agentAddress2, "
|
|
||||||
+ " a.agent_city as agentCity, a.agent_pincode as agentPinCode, a.agent_telephone as agentTelephone, "
|
|
||||||
+ " a.agent_saving_account_type as agentSavingAccountType, a.agent_saving_account_no as agentSavingAccountNo, "
|
|
||||||
+ " a.agent_opening_date as agentOpeningDate, a.agent_birth_date as agentBirthDate, "
|
|
||||||
+ " a.agent_current_balance as agentCurrentBalance, a.agent_opening_balance as agentOpeningBalance, "
|
|
||||||
+ " a.home_branch as homeBranch, a.saving_branch as savingBranch, a.tds_applicable as tdsApplicable, "
|
|
||||||
+ " a.tds_percent as tdsPercent, a.current_tds as currentTds, a.tds_amount as tdsAmount, "
|
|
||||||
+ " a.transfer_branch as transferBranch, a.transfer_amount_account_type as transferAmountAccountType, "
|
|
||||||
+ " a.transfer_account_no as transferAccountNo, a.collector_commission_calculation as commissionCalculation, "
|
|
||||||
+ " a.collector_limit_amount as limitAmount, a.machine_rent as machineRent, "
|
|
||||||
+ " a.agent_security_branch as agentSecurityBranch, a.agent_security_account_type as agentSecurityAccountType, "
|
|
||||||
+ " a.agent_security_account_no as agentSecurityAccountNo, a.commission as commission, "
|
|
||||||
+ " a.other_commission as otherCommission, a.agent_deposit as agentDeposit, "
|
|
||||||
+ " a.festival_advance as festivalAdvance, a.other_deduction as otherDeduction "
|
|
||||||
+ " from m_dds_agent a join m_client c on c.id = a.client_id";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AgentData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
return AgentData.instance(rs.getLong("id"), rs.getLong("clientId"), rs.getString("clientName"), rs.getString("agentCode"),
|
|
||||||
rs.getString("agentNameId"), rs.getString("agentAddress1"), rs.getString("agentAddress2"),
|
|
||||||
rs.getString("agentCity"), rs.getString("agentPinCode"), rs.getString("agentTelephone"),
|
|
||||||
rs.getString("agentSavingAccountType"), rs.getString("agentSavingAccountNo"),
|
|
||||||
JdbcSupport.getLocalDate(rs, "agentOpeningDate"), JdbcSupport.getLocalDate(rs, "agentBirthDate"),
|
|
||||||
rs.getBigDecimal("agentCurrentBalance"), rs.getBigDecimal("agentOpeningBalance"), rs.getString("homeBranch"),
|
|
||||||
rs.getString("savingBranch"), rs.getBoolean("tdsApplicable"), rs.getBigDecimal("tdsPercent"),
|
|
||||||
rs.getBigDecimal("currentTds"), rs.getBigDecimal("tdsAmount"), rs.getString("transferBranch"),
|
|
||||||
rs.getString("transferAmountAccountType"), rs.getString("transferAccountNo"),
|
|
||||||
rs.getBoolean("commissionCalculation"), rs.getBigDecimal("limitAmount"), rs.getBigDecimal("machineRent"),
|
|
||||||
rs.getString("agentSecurityBranch"), rs.getString("agentSecurityAccountType"),
|
|
||||||
rs.getString("agentSecurityAccountNo"), rs.getBigDecimal("commission"), rs.getBigDecimal("otherCommission"),
|
|
||||||
rs.getBigDecimal("agentDeposit"), rs.getBigDecimal("festivalAdvance"), rs.getBigDecimal("otherDeduction"),
|
|
||||||
new ArrayList<>(), new ArrayList<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AgentLimitMapper implements RowMapper<Map.Entry<Long, AgentLimitData>> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " l.agent_id as agentId, l.id as id, l.from_date as fromDate, l.to_date as toDate, "
|
|
||||||
+ " l.limit_amount as limitAmount from m_dds_agent_limit l";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map.Entry<Long, AgentLimitData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
final Long agentId = rs.getLong("agentId");
|
|
||||||
final AgentLimitData data = AgentLimitData.instance(rs.getLong("id"), JdbcSupport.getLocalDate(rs, "fromDate"),
|
|
||||||
JdbcSupport.getLocalDate(rs, "toDate"), rs.getBigDecimal("limitAmount"));
|
|
||||||
return Map.entry(agentId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AgentGuarantorMapper implements RowMapper<Map.Entry<Long, AgentGuarantorData>> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " g.agent_id as agentId, g.id as id, g.client_id as clientId, g.guarantor_name as guarantorName, "
|
|
||||||
+ " g.address1 as address1, g.address2 as address2, g.city as city, g.pin_code as pinCode, "
|
|
||||||
+ " g.telephone as telephone from m_dds_agent_guarantor g";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map.Entry<Long, AgentGuarantorData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
final Long agentId = rs.getLong("agentId");
|
|
||||||
final AgentGuarantorData data = AgentGuarantorData.instance(rs.getLong("id"), rs.getLong("clientId"),
|
|
||||||
rs.getString("guarantorName"), rs.getString("address1"), rs.getString("address2"), rs.getString("city"),
|
|
||||||
rs.getString("pinCode"), rs.getString("telephone"));
|
|
||||||
return Map.entry(agentId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<AgentData> retrieveAll() {
|
|
||||||
final String sql = "select " + AGENT_MAPPER.schema() + " order by a.id";
|
|
||||||
final List<AgentData> agents = this.jdbcTemplate.query(sql, AGENT_MAPPER); // NOSONAR
|
|
||||||
return attachChildren(agents);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AgentData retrieveOne(final Long agentId) {
|
|
||||||
try {
|
|
||||||
final String sql = "select " + AGENT_MAPPER.schema() + " where a.id = ?";
|
|
||||||
final AgentData agent = this.jdbcTemplate.queryForObject(sql, AGENT_MAPPER, agentId); // NOSONAR
|
|
||||||
return attachChildren(List.of(agent)).iterator().next();
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
throw new AgentNotFoundException(agentId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<AgentData> attachChildren(final List<AgentData> agents) {
|
|
||||||
if (agents.isEmpty()) {
|
|
||||||
return agents;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String limitSql = "select " + LIMIT_MAPPER.schema() + " order by l.id";
|
|
||||||
final List<Map.Entry<Long, AgentLimitData>> limitRows = this.jdbcTemplate.query(limitSql, LIMIT_MAPPER); // NOSONAR
|
|
||||||
final Map<Long, List<AgentLimitData>> limitsByAgentId = new LinkedHashMap<>();
|
|
||||||
for (final Map.Entry<Long, AgentLimitData> row : limitRows) {
|
|
||||||
limitsByAgentId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final String guarantorSql = "select " + GUARANTOR_MAPPER.schema() + " order by g.id";
|
|
||||||
final List<Map.Entry<Long, AgentGuarantorData>> guarantorRows = this.jdbcTemplate.query(guarantorSql, GUARANTOR_MAPPER); // NOSONAR
|
|
||||||
final Map<Long, List<AgentGuarantorData>> guarantorsByAgentId = new LinkedHashMap<>();
|
|
||||||
for (final Map.Entry<Long, AgentGuarantorData> row : guarantorRows) {
|
|
||||||
guarantorsByAgentId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<AgentData> result = new ArrayList<>(agents.size());
|
|
||||||
for (final AgentData agent : agents) {
|
|
||||||
final List<AgentLimitData> limits = limitsByAgentId.getOrDefault(agent.getId(), new ArrayList<>());
|
|
||||||
final List<AgentGuarantorData> guarantors = guarantorsByAgentId.getOrDefault(agent.getId(), new ArrayList<>());
|
|
||||||
result.add(AgentData.instance(agent.getId(), agent.getClientId(), agent.getClientName(), agent.getAgentCode(),
|
|
||||||
agent.getAgentNameId(), agent.getAgentAddress1(), agent.getAgentAddress2(), agent.getAgentCity(),
|
|
||||||
agent.getAgentPinCode(), agent.getAgentTelephone(), agent.getAgentSavingAccountType(),
|
|
||||||
agent.getAgentSavingAccountNo(), agent.getAgentOpeningDate(), agent.getAgentBirthDate(),
|
|
||||||
agent.getAgentCurrentBalance(), agent.getAgentOpeningBalance(), agent.getHomeBranch(), agent.getSavingBranch(),
|
|
||||||
agent.getTdsApplicable(), agent.getTdsPercent(), agent.getCurrentTds(), agent.getTdsAmount(),
|
|
||||||
agent.getTransferBranch(), agent.getTransferAmountAccountType(), agent.getTransferAccountNo(),
|
|
||||||
agent.getCommissionCalculation(), agent.getLimit(), agent.getMachineRent(), agent.getAgentSecurityBranch(),
|
|
||||||
agent.getAgentSecurityAccountType(), agent.getAgentSecurityAccountNo(), agent.getCommission(),
|
|
||||||
agent.getOtherCommission(), agent.getAgentDeposit(), agent.getFestivalAdvance(), agent.getOtherDeduction(),
|
|
||||||
limits, guarantors));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.service;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
|
|
||||||
public interface AgentWritePlatformService {
|
|
||||||
|
|
||||||
CommandProcessingResult createAgent(JsonCommand command);
|
|
||||||
|
|
||||||
CommandProcessingResult updateAgent(Long agentId, JsonCommand command);
|
|
||||||
}
|
|
||||||
-187
@@ -1,187 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.service;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import jakarta.persistence.PersistenceException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentGuarantor;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentLimit;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
|
||||||
import org.apache.fineract.organisation.agent.serialization.AgentCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AgentWritePlatformServiceJpaRepositoryImpl implements AgentWritePlatformService {
|
|
||||||
|
|
||||||
private final AgentCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
|
||||||
private final AgentRepository agentRepository;
|
|
||||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult createAgent(final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
|
||||||
|
|
||||||
final Long clientId = command.longValueOfParameterNamed("clientId");
|
|
||||||
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
|
||||||
|
|
||||||
final String agentCode = generateNextAgentCode();
|
|
||||||
final Agent agent = Agent.fromJson(client, agentCode, command);
|
|
||||||
|
|
||||||
addLimitsFromCommand(agent, command);
|
|
||||||
addGuarantorsFromCommand(agent, command);
|
|
||||||
|
|
||||||
this.agentRepository.saveAndFlush(agent);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(agent.getId()) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
handleAgentDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
|
||||||
handleAgentDataIntegrityIssues(throwable, dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult updateAgent(final Long agentId, final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
|
||||||
|
|
||||||
final Agent agentForUpdate = this.agentRepository.findById(agentId).orElseThrow(() -> new AgentNotFoundException(agentId));
|
|
||||||
final Map<String, Object> changesOnly = agentForUpdate.update(command);
|
|
||||||
|
|
||||||
if (changesOnly.containsKey("clientId")) {
|
|
||||||
final Long newClientId = (Long) changesOnly.get("clientId");
|
|
||||||
final Client newClient = this.clientRepositoryWrapper.findOneWithNotFoundDetection(newClientId);
|
|
||||||
agentForUpdate.changeClient(newClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.parameterExists("limits")) {
|
|
||||||
agentForUpdate.resetLimits();
|
|
||||||
addLimitsFromCommand(agentForUpdate, command);
|
|
||||||
changesOnly.put("limits", command.jsonFragment("limits"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.parameterExists("guarantors")) {
|
|
||||||
agentForUpdate.resetGuarantors();
|
|
||||||
addGuarantorsFromCommand(agentForUpdate, command);
|
|
||||||
changesOnly.put("guarantors", command.jsonFragment("guarantors"));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.agentRepository.saveAndFlush(agentForUpdate);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(agentId).with(changesOnly)
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
handleAgentDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
|
||||||
handleAgentDataIntegrityIssues(throwable, dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addLimitsFromCommand(final Agent agent, final JsonCommand command) {
|
|
||||||
if (!command.parameterExists("limits")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final JsonArray limitsArray = command.arrayOfParameterNamed("limits");
|
|
||||||
if (limitsArray == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < limitsArray.size(); i++) {
|
|
||||||
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
|
|
||||||
final LocalDate fromDate = LocalDate.parse(limitJson.get("fromDate").getAsString());
|
|
||||||
final LocalDate toDate = LocalDate.parse(limitJson.get("toDate").getAsString());
|
|
||||||
final BigDecimal limit = limitJson.get("limit").getAsBigDecimal();
|
|
||||||
agent.addLimit(new AgentLimit(fromDate, toDate, limit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addGuarantorsFromCommand(final Agent agent, final JsonCommand command) {
|
|
||||||
if (!command.parameterExists("guarantors")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final JsonArray guarantorsArray = command.arrayOfParameterNamed("guarantors");
|
|
||||||
if (guarantorsArray == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < guarantorsArray.size(); i++) {
|
|
||||||
final JsonObject guarantorJson = guarantorsArray.get(i).getAsJsonObject();
|
|
||||||
final Long guarantorClientId = guarantorJson.get("clientId").getAsLong();
|
|
||||||
final String guarantorName = guarantorJson.get("guarantorName").getAsString();
|
|
||||||
final String address1 = guarantorJson.get("address1").getAsString();
|
|
||||||
final String address2 = guarantorJson.has("address2") && !guarantorJson.get("address2").isJsonNull()
|
|
||||||
? guarantorJson.get("address2").getAsString()
|
|
||||||
: null;
|
|
||||||
final String city = guarantorJson.get("city").getAsString();
|
|
||||||
final String pinCode = guarantorJson.get("pinCode").getAsString();
|
|
||||||
final String telephone = guarantorJson.has("telephone") && !guarantorJson.get("telephone").isJsonNull()
|
|
||||||
? guarantorJson.get("telephone").getAsString()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
agent.addGuarantor(new AgentGuarantor(guarantorClientId, guarantorName, address1, address2, city, pinCode, telephone));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateNextAgentCode() {
|
|
||||||
final Long maxSuffix = this.agentRepository.retrieveMaxAgentCodeSuffix();
|
|
||||||
final long nextSuffix = (maxSuffix == null ? 0L : maxSuffix) + 1L;
|
|
||||||
return String.format("AG%04d", nextSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleAgentDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
|
||||||
if (realCause.getMessage() != null && realCause.getMessage().contains("agent_code")) {
|
|
||||||
throw new PlatformDataIntegrityException("error.msg.agent.duplicate.agentCode", "An agent with the generated code already exists, please retry");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("Error occured.", dve);
|
|
||||||
throw ErrorHandler.getMappable(dve, "error.msg.agent.unknown.data.integrity.issue",
|
|
||||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.agent.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.serialization.AgentCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformService;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentReadPlatformServiceImpl;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformService;
|
|
||||||
import org.apache.fineract.organisation.agent.service.AgentWritePlatformServiceJpaRepositoryImpl;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class OrganisationAgentConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(AgentReadPlatformService.class)
|
|
||||||
public AgentReadPlatformService agentReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new AgentReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(AgentWritePlatformService.class)
|
|
||||||
public AgentWritePlatformService agentWritePlatformService(AgentCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
|
||||||
AgentRepository agentRepository, ClientRepositoryWrapper clientRepositoryWrapper) {
|
|
||||||
return new AgentWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, agentRepository, clientRepositoryWrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.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.GET;
|
|
||||||
import jakarta.ws.rs.POST;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/commissionslabs")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Commission Slabs", description = "The agent commission chart: ACCOUNT slabs (commission percentage on collection volume) and COMMISSION slabs (the override a senior agent earns on a sub-agent's commission). Append-only reference data - no update or delete API.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CommissionSlabsApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "COMMISSIONSLAB";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final CommissionSlabReadPlatformService readPlatformService;
|
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve DDS Commission Slabs", description = "Returns the full commission slab chart.")
|
|
||||||
public Collection<CommissionSlabData> retrieveAll() {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Add DDS Commission Slabs", description = "Mandatory fields: slabs (array of {slabType: ACCOUNT|COMMISSION, "
|
|
||||||
+ "slabFrom, slabTo, commissionPercent}). Adds new slab rows to the chart - it does not replace or edit existing rows.")
|
|
||||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.createCommissionSlabs() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.domain;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface CommissionSlabRepository extends JpaRepository<CommissionSlab, Long> {
|
|
||||||
|
|
||||||
@Query("select s from CommissionSlab s where s.slabType = :slabType and :amount between s.slabFrom and s.slabTo")
|
|
||||||
List<CommissionSlab> findMatchingSlabs(@Param("slabType") String slabType, @Param("amount") BigDecimal amount);
|
|
||||||
}
|
|
||||||
-113
@@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.serialization;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public final class CommissionSlabCommandFromApiJsonDeserializer {
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_SLAB_TYPES = new HashSet<>(Arrays.asList("ACCOUNT", "COMMISSION"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("slabs", "dateFormat", "locale"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_SLAB_PARAMETERS = new HashSet<>(
|
|
||||||
Arrays.asList("slabType", "slabFrom", "slabTo", "commissionPercent"));
|
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CommissionSlabCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForCreate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("commissionslab");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
if (!this.fromApiJsonHelper.parameterExists("slabs", element)) {
|
|
||||||
v.reset().parameter("slabs").value(null).notNull();
|
|
||||||
} else {
|
|
||||||
final JsonObject topLevel = element.getAsJsonObject();
|
|
||||||
final var slabsArray = topLevel.getAsJsonArray("slabs");
|
|
||||||
v.reset().parameter("slabs").value(slabsArray).jsonArrayNotEmpty();
|
|
||||||
|
|
||||||
for (int i = 0; i < slabsArray.size(); i++) {
|
|
||||||
final JsonObject slabJson = slabsArray.get(i).getAsJsonObject();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(slabJson, SUPPORTED_SLAB_PARAMETERS);
|
|
||||||
|
|
||||||
final String slabType = extractString(slabJson, "slabType");
|
|
||||||
v.reset().parameterAtIndexArray("slabType", i).value(slabType).notBlank()
|
|
||||||
.isOneOfTheseStringValues(SUPPORTED_SLAB_TYPES.toArray());
|
|
||||||
v.reset().parameterAtIndexArray("slabFrom", i).value(extractBigDecimal(slabJson, "slabFrom")).notNull()
|
|
||||||
.zeroOrPositiveAmount();
|
|
||||||
v.reset().parameterAtIndexArray("slabTo", i).value(extractBigDecimal(slabJson, "slabTo")).notNull()
|
|
||||||
.zeroOrPositiveAmount();
|
|
||||||
v.reset().parameterAtIndexArray("commissionPercent", i).value(extractBigDecimal(slabJson, "commissionPercent"))
|
|
||||||
.notNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractString(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
|
||||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
|
||||||
dataValidationErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
|
||||||
|
|
||||||
public interface CommissionSlabReadPlatformService {
|
|
||||||
|
|
||||||
Collection<CommissionSlabData> retrieveAll();
|
|
||||||
}
|
|
||||||
-60
@@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.service;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CommissionSlabReadPlatformServiceImpl implements CommissionSlabReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
private static final CommissionSlabMapper MAPPER = new CommissionSlabMapper();
|
|
||||||
|
|
||||||
private static final class CommissionSlabMapper implements RowMapper<CommissionSlabData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " s.id as id, s.slab_type as slabType, s.slab_from as slabFrom, s.slab_to as slabTo, "
|
|
||||||
+ " s.commission_percent as commissionPercent from m_dds_commission_slab s";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommissionSlabData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
return CommissionSlabData.builder() //
|
|
||||||
.id(rs.getLong("id")) //
|
|
||||||
.slabType(rs.getString("slabType")) //
|
|
||||||
.slabFrom(rs.getBigDecimal("slabFrom")) //
|
|
||||||
.slabTo(rs.getBigDecimal("slabTo")) //
|
|
||||||
.commissionPercent(rs.getBigDecimal("commissionPercent")) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<CommissionSlabData> retrieveAll() {
|
|
||||||
final String sql = "select " + MAPPER.schema() + " order by s.slab_type, s.slab_from";
|
|
||||||
return this.jdbcTemplate.query(sql, MAPPER); // NOSONAR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.service;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
|
|
||||||
public interface CommissionSlabWritePlatformService {
|
|
||||||
|
|
||||||
CommandProcessingResult createCommissionSlabs(JsonCommand command);
|
|
||||||
}
|
|
||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.service;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import jakarta.persistence.PersistenceException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlab;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.serialization.CommissionSlabCommandFromApiJsonDeserializer;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CommissionSlabWritePlatformServiceJpaRepositoryImpl implements CommissionSlabWritePlatformService {
|
|
||||||
|
|
||||||
private final CommissionSlabCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
|
||||||
private final CommissionSlabRepository commissionSlabRepository;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult createCommissionSlabs(final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
|
||||||
|
|
||||||
final List<CommissionSlab> slabs = new ArrayList<>();
|
|
||||||
final JsonArray slabsArray = command.arrayOfParameterNamed("slabs");
|
|
||||||
for (int i = 0; i < slabsArray.size(); i++) {
|
|
||||||
final JsonObject slabJson = slabsArray.get(i).getAsJsonObject();
|
|
||||||
slabs.add(new CommissionSlab(slabJson.get("slabType").getAsString(), slabJson.get("slabFrom").getAsBigDecimal(),
|
|
||||||
slabJson.get("slabTo").getAsBigDecimal(), slabJson.get("commissionPercent").getAsBigDecimal()));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.commissionSlabRepository.saveAllAndFlush(slabs);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(slabs.isEmpty() ? null : slabs.get(0).getId()) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
log.error("Error occured.", dve);
|
|
||||||
throw ErrorHandler.getMappable(dve, "error.msg.commissionslab.unknown.data.integrity.issue",
|
|
||||||
"Unknown data integrity issue with resource: " + ExceptionUtils.getRootCauseMessage(dve));
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
log.error("Error occured.", dve);
|
|
||||||
throw ErrorHandler.getMappable(dve, "error.msg.commissionslab.unknown.data.integrity.issue",
|
|
||||||
"Unknown data integrity issue with resource: " + ExceptionUtils.getRootCauseMessage(dve));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.organisation.commissionslab.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.serialization.CommissionSlabCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformService;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabReadPlatformServiceImpl;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabWritePlatformService;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.service.CommissionSlabWritePlatformServiceJpaRepositoryImpl;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class OrganisationCommissionSlabConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(CommissionSlabReadPlatformService.class)
|
|
||||||
public CommissionSlabReadPlatformService commissionSlabReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new CommissionSlabReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(CommissionSlabWritePlatformService.class)
|
|
||||||
public CommissionSlabWritePlatformService commissionSlabWritePlatformService(
|
|
||||||
CommissionSlabCommandFromApiJsonDeserializer fromApiJsonDeserializer, CommissionSlabRepository commissionSlabRepository) {
|
|
||||||
return new CommissionSlabWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, commissionSlabRepository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+16
-3
@@ -357,9 +357,14 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
accountsSummary.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
|
||||||
accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, ");
|
accountsSummary.append("curr.display_symbol as currencyDisplaySymbol, ");
|
||||||
accountsSummary.append("sa.product_id as productId, p.name as productName, p.short_name as shortProductName, ");
|
accountsSummary.append("sa.product_id as productId, p.name as productName, p.short_name as shortProductName, ");
|
||||||
accountsSummary.append("sa.deposit_type_enum as depositType ");
|
accountsSummary.append("sa.deposit_type_enum as depositType, ");
|
||||||
|
// Renewal-chain identity for Fixed Deposits: the stable customer-facing FD number and
|
||||||
|
// this row's cycle within the chain, so a client's account list can collapse a renewed
|
||||||
|
// FD into one entry rather than listing every term as a separate account.
|
||||||
|
accountsSummary.append("dtp.fd_account_number as fdAccountNumber, dtp.renewal_cycle_no as renewalCycleNo ");
|
||||||
accountsSummary.append("from m_savings_account sa ");
|
accountsSummary.append("from m_savings_account sa ");
|
||||||
accountsSummary.append("join m_savings_product as p on p.id = sa.product_id ");
|
accountsSummary.append("join m_savings_product as p on p.id = sa.product_id ");
|
||||||
|
accountsSummary.append("left join m_deposit_account_term_and_preclosure dtp on dtp.savings_account_id = sa.id ");
|
||||||
accountsSummary.append("join m_currency curr on curr.code = sa.currency_code ");
|
accountsSummary.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||||
accountsSummary.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid ");
|
accountsSummary.append("left join m_appuser sbu on sbu.id = sa.submittedon_userid ");
|
||||||
accountsSummary.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
|
accountsSummary.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
|
||||||
@@ -453,9 +458,15 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname,
|
approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate, activatedByUsername, activatedByFirstname,
|
||||||
activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname);
|
activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname);
|
||||||
|
|
||||||
|
// Null for a savings/current account and for an FD opened before this column existed;
|
||||||
|
// callers fall back to accountNo, so an FD that has never been renewed still reads as
|
||||||
|
// its own FD number.
|
||||||
|
final String fdAccountNumber = rs.getString("fdAccountNumber");
|
||||||
|
final Integer renewalCycleNo = JdbcSupport.getInteger(rs, "renewalCycleNo");
|
||||||
|
|
||||||
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
|
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
|
||||||
accountBalance, onHoldFunds, onHoldAmount, availableBalance, accountTypeData, timeline, depositTypeData, subStatus,
|
accountBalance, onHoldFunds, onHoldAmount, availableBalance, accountTypeData, timeline, depositTypeData, subStatus,
|
||||||
lastActiveTransactionDate);
|
lastActiveTransactionDate, fdAccountNumber, renewalCycleNo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,8 +578,10 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
|||||||
|
|
||||||
final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400);
|
final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400);
|
||||||
|
|
||||||
|
// Current accounts have no deposit term, so no FD renewal chain to report.
|
||||||
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
|
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
|
||||||
accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate);
|
accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate,
|
||||||
|
null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-102
@@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.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.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.core.MediaType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/batchruns")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Batch Runs", description = "The Interest/Commission calculate-then-post batch: Generate previews a run's rows without changing any balance; Post applies exactly those previewed rows. A run can only be posted once.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class BatchRunsApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "DDSBATCH";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final BatchRunReadPlatformService readPlatformService;
|
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve DDS Batch Runs", description = "Returns the list of DDS batch runs, most recent first.")
|
|
||||||
public Collection<BatchRunData> retrieveAll() {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{batchRunId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve a DDS Batch Run", description = "Returns the details of a batch run, including its previewed/posted rows.")
|
|
||||||
public BatchRunData retrieveOne(@PathParam("batchRunId") @Parameter(description = "batchRunId") final Long batchRunId) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveOne(batchRunId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Generate a DDS Batch Run", description = "Mandatory fields: jobType (INTEREST_CALCULATION, "
|
|
||||||
+ "OD_INTEREST_CALCULATION or COMMISSION_CALCULATION), branchScope (All or Selected), jobDate, agentFrom, agentTo\n"
|
|
||||||
+ "\n" + "Optional fields: partyFrom, partyTo (interest jobs only)")
|
|
||||||
public CommandProcessingResult generate(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.generateDdsBatchRun() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("{batchRunId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Post a DDS Batch Run", description = "Applies exactly the rows a prior Generate call previewed: credits "
|
|
||||||
+ "each row's amount to the Party (interest jobs) or to the Agent's running commission total (commission jobs). "
|
|
||||||
+ "Fails if the run has already been posted.")
|
|
||||||
public CommandProcessingResult post(@PathParam("batchRunId") @Parameter(description = "batchRunId") final Long batchRunId,
|
|
||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.postDdsBatchRun(batchRunId) //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "DDSBATCH", action = "GENERATE")
|
|
||||||
public class GenerateBatchRunCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final BatchRunWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public GenerateBatchRunCommandHandler(final BatchRunWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.generateBatchRun(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "DDSBATCH", action = "POST")
|
|
||||||
public class PostBatchRunCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final BatchRunWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public PostBatchRunCommandHandler(final BatchRunWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.postBatchRun(command.entityId(), command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.serialization;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public final class BatchRunCommandFromApiJsonDeserializer {
|
|
||||||
|
|
||||||
public static final Set<String> SUPPORTED_JOB_TYPES = new HashSet<>(
|
|
||||||
Arrays.asList("INTEREST_CALCULATION", "OD_INTEREST_CALCULATION", "COMMISSION_CALCULATION"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_BRANCH_SCOPES = new HashSet<>(Arrays.asList("All", "Selected"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("jobType", "branchScope", "officeNames",
|
|
||||||
"jobDate", "agentFrom", "agentTo", "partyFrom", "partyTo", "dateFormat", "locale"));
|
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public BatchRunCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForGenerate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("ddsbatchrun");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
v.reset().parameter("jobType").value(this.fromApiJsonHelper.extractStringNamed("jobType", element)).notBlank()
|
|
||||||
.isOneOfTheseStringValues(SUPPORTED_JOB_TYPES.toArray());
|
|
||||||
final String branchScope = this.fromApiJsonHelper.extractStringNamed("branchScope", element);
|
|
||||||
v.reset().parameter("branchScope").value(branchScope).notBlank().isOneOfTheseStringValues(SUPPORTED_BRANCH_SCOPES.toArray());
|
|
||||||
|
|
||||||
if ("Selected".equals(branchScope)) {
|
|
||||||
v.reset().parameter("officeNames").value(this.fromApiJsonHelper.extractJsonArrayNamed("officeNames", element))
|
|
||||||
.jsonArrayNotEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
v.reset().parameter("jobDate").value(this.fromApiJsonHelper.extractLocalDateNamed("jobDate", element)).notNull();
|
|
||||||
v.reset().parameter("agentFrom").value(this.fromApiJsonHelper.extractLongNamed("agentFrom", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
v.reset().parameter("agentTo").value(this.fromApiJsonHelper.extractLongNamed("agentTo", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("partyFrom", element)) {
|
|
||||||
v.reset().parameter("partyFrom").value(this.fromApiJsonHelper.extractLongNamed("partyFrom", element)).ignoreIfNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("partyTo", element)) {
|
|
||||||
v.reset().parameter("partyTo").value(this.fromApiJsonHelper.extractLongNamed("partyTo", element)).ignoreIfNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
|
||||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
|
||||||
dataValidationErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
|
||||||
|
|
||||||
public interface BatchRunReadPlatformService {
|
|
||||||
|
|
||||||
Collection<BatchRunData> retrieveAll();
|
|
||||||
|
|
||||||
BatchRunData retrieveOne(Long batchRunId);
|
|
||||||
}
|
|
||||||
-137
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.service;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunData;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.data.BatchRunRowData;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class BatchRunReadPlatformServiceImpl implements BatchRunReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
private static final BatchRunMapper BATCH_RUN_MAPPER = new BatchRunMapper();
|
|
||||||
private static final BatchRunRowMapper ROW_MAPPER = new BatchRunRowMapper();
|
|
||||||
|
|
||||||
private static final class BatchRunMapper implements RowMapper<BatchRunData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " b.id as id, b.job_type as jobType, b.branch_scope as branchScope, b.job_date as jobDate, "
|
|
||||||
+ " b.agent_from as agentFrom, b.agent_to as agentTo, b.party_from as partyFrom, "
|
|
||||||
+ " b.party_to as partyTo, b.status as status, b.total_rows as totalRows, b.total_amount as totalAmount, "
|
|
||||||
+ " b.generated_on_date as generatedOnDate, b.posted_on_date as postedOnDate from m_dds_batch_run b";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BatchRunData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
return BatchRunData.builder() //
|
|
||||||
.id(rs.getLong("id")) //
|
|
||||||
.jobType(rs.getString("jobType")) //
|
|
||||||
.branchScope(rs.getString("branchScope")) //
|
|
||||||
.jobDate(JdbcSupport.getLocalDate(rs, "jobDate")) //
|
|
||||||
.agentFrom(rs.getLong("agentFrom")) //
|
|
||||||
.agentTo(rs.getLong("agentTo")) //
|
|
||||||
.partyFrom(JdbcSupport.getLong(rs, "partyFrom")) //
|
|
||||||
.partyTo(JdbcSupport.getLong(rs, "partyTo")) //
|
|
||||||
.status(rs.getString("status")) //
|
|
||||||
.totalRows(rs.getInt("totalRows")) //
|
|
||||||
.totalAmount(rs.getBigDecimal("totalAmount")) //
|
|
||||||
.generatedOnDate(JdbcSupport.getLocalDate(rs, "generatedOnDate")) //
|
|
||||||
.postedOnDate(JdbcSupport.getLocalDate(rs, "postedOnDate")) //
|
|
||||||
.rows(new ArrayList<>()) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class BatchRunRowMapper implements RowMapper<Map.Entry<Long, BatchRunRowData>> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " r.batch_run_id as batchRunId, r.id as id, r.party_id as partyId, p.party_code as partyCode, "
|
|
||||||
+ " p.party_name as partyName, r.agent_id as agentId, a.agent_code as agentCode, "
|
|
||||||
+ " r.amount as amount from m_dds_batch_run_row r " + " left join m_dds_party p on p.id = r.party_id "
|
|
||||||
+ " join m_dds_agent a on a.id = r.agent_id";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map.Entry<Long, BatchRunRowData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
final Long batchRunId = rs.getLong("batchRunId");
|
|
||||||
final BatchRunRowData data = BatchRunRowData.builder() //
|
|
||||||
.id(rs.getLong("id")) //
|
|
||||||
.partyId(JdbcSupport.getLong(rs, "partyId")) //
|
|
||||||
.partyCode(rs.getString("partyCode")) //
|
|
||||||
.partyName(rs.getString("partyName")) //
|
|
||||||
.agentId(rs.getLong("agentId")) //
|
|
||||||
.agentCode(rs.getString("agentCode")) //
|
|
||||||
.amount(rs.getBigDecimal("amount")) //
|
|
||||||
.build();
|
|
||||||
return Map.entry(batchRunId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<BatchRunData> retrieveAll() {
|
|
||||||
final String sql = "select " + BATCH_RUN_MAPPER.schema() + " order by b.id desc";
|
|
||||||
final List<BatchRunData> batchRuns = this.jdbcTemplate.query(sql, BATCH_RUN_MAPPER); // NOSONAR
|
|
||||||
return attachRows(batchRuns);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BatchRunData retrieveOne(final Long batchRunId) {
|
|
||||||
try {
|
|
||||||
final String sql = "select " + BATCH_RUN_MAPPER.schema() + " where b.id = ?";
|
|
||||||
final BatchRunData batchRun = this.jdbcTemplate.queryForObject(sql, BATCH_RUN_MAPPER, batchRunId); // NOSONAR
|
|
||||||
return attachRows(List.of(batchRun)).iterator().next();
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
throw new BatchRunNotFoundException(batchRunId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<BatchRunData> attachRows(final List<BatchRunData> batchRuns) {
|
|
||||||
if (batchRuns.isEmpty()) {
|
|
||||||
return batchRuns;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String sql = "select " + ROW_MAPPER.schema() + " order by r.id";
|
|
||||||
final List<Map.Entry<Long, BatchRunRowData>> rows = this.jdbcTemplate.query(sql, ROW_MAPPER); // NOSONAR
|
|
||||||
|
|
||||||
final Map<Long, List<BatchRunRowData>> rowsByBatchRunId = new LinkedHashMap<>();
|
|
||||||
for (final Map.Entry<Long, BatchRunRowData> row : rows) {
|
|
||||||
rowsByBatchRunId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<BatchRunData> result = new ArrayList<>(batchRuns.size());
|
|
||||||
for (final BatchRunData b : batchRuns) {
|
|
||||||
result.add(b.toBuilder().rows(rowsByBatchRunId.getOrDefault(b.getId(), new ArrayList<>())).build());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.service;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
|
|
||||||
public interface BatchRunWritePlatformService {
|
|
||||||
|
|
||||||
CommandProcessingResult generateBatchRun(JsonCommand command);
|
|
||||||
|
|
||||||
CommandProcessingResult postBatchRun(Long batchRunId, JsonCommand command);
|
|
||||||
}
|
|
||||||
-227
@@ -1,227 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.service;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlab;
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRun;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRow;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunAlreadyPostedException;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.exception.BatchRunNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.serialization.BatchRunCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransaction;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class BatchRunWritePlatformServiceJpaRepositoryImpl implements BatchRunWritePlatformService {
|
|
||||||
|
|
||||||
private static final BigDecimal DAYS_IN_YEAR = BigDecimal.valueOf(365);
|
|
||||||
private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
|
|
||||||
|
|
||||||
private final BatchRunCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
|
||||||
private final BatchRunRepository batchRunRepository;
|
|
||||||
private final PartyRepository partyRepository;
|
|
||||||
private final AgentRepository agentRepository;
|
|
||||||
private final PartyTransactionRepository partyTransactionRepository;
|
|
||||||
private final CommissionSlabRepository commissionSlabRepository;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult generateBatchRun(final JsonCommand command) {
|
|
||||||
|
|
||||||
this.fromApiJsonDeserializer.validateForGenerate(command.json());
|
|
||||||
|
|
||||||
final String jobType = command.stringValueOfParameterNamed("jobType");
|
|
||||||
final String branchScope = command.stringValueOfParameterNamed("branchScope");
|
|
||||||
final LocalDate jobDate = command.localDateValueOfParameterNamed("jobDate");
|
|
||||||
final Long agentFrom = command.longValueOfParameterNamed("agentFrom");
|
|
||||||
final Long agentTo = command.longValueOfParameterNamed("agentTo");
|
|
||||||
final Long partyFrom = command.parameterExists("partyFrom") ? command.longValueOfParameterNamed("partyFrom") : 1L;
|
|
||||||
final Long partyTo = command.parameterExists("partyTo") ? command.longValueOfParameterNamed("partyTo") : Long.MAX_VALUE;
|
|
||||||
|
|
||||||
final Set<String> officeNames = "Selected".equals(branchScope) ? extractOfficeNames(command) : null;
|
|
||||||
|
|
||||||
final BatchRun batchRun = new BatchRun(jobType, branchScope, jobDate, agentFrom, agentTo, partyFrom, partyTo, LocalDate.now());
|
|
||||||
|
|
||||||
switch (jobType) {
|
|
||||||
case "INTEREST_CALCULATION" -> generateInterestRows(batchRun, agentFrom, agentTo, partyFrom, partyTo, jobDate, false,
|
|
||||||
officeNames);
|
|
||||||
case "OD_INTEREST_CALCULATION" -> generateInterestRows(batchRun, agentFrom, agentTo, partyFrom, partyTo, jobDate, true,
|
|
||||||
officeNames);
|
|
||||||
case "COMMISSION_CALCULATION" -> generateCommissionRows(batchRun, agentFrom, agentTo, jobDate, officeNames);
|
|
||||||
default -> throw new IllegalStateException("Unsupported DDS batch job type: " + jobType);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.batchRunRepository.saveAndFlush(batchRun);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(batchRun.getId()) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parses the "Branch" checklist selection - null/empty means no filter ("All"). Matched
|
|
||||||
* against Agent.homeBranch by name, since that field is plain text (not a real Office FK)
|
|
||||||
* mirroring the same free-text branch concept the legacy screen itself used. */
|
|
||||||
private Set<String> extractOfficeNames(final JsonCommand command) {
|
|
||||||
if (!command.parameterExists("officeNames")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final JsonArray officeNamesArray = command.arrayOfParameterNamed("officeNames");
|
|
||||||
final Set<String> officeNames = new HashSet<>();
|
|
||||||
for (int i = 0; i < officeNamesArray.size(); i++) {
|
|
||||||
officeNames.add(officeNamesArray.get(i).getAsString().trim().toUpperCase());
|
|
||||||
}
|
|
||||||
return officeNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean matchesSelectedBranch(final String homeBranch, final Set<String> officeNames) {
|
|
||||||
if (officeNames == null || officeNames.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return homeBranch != null && officeNames.contains(homeBranch.trim().toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateInterestRows(final BatchRun batchRun, final Long agentFrom, final Long agentTo, final Long partyFrom,
|
|
||||||
final Long partyTo, final LocalDate jobDate, final boolean odOnly, final Set<String> officeNames) {
|
|
||||||
final List<Party> parties = odOnly
|
|
||||||
? this.partyRepository.findForOdInterestBatch(agentFrom, agentTo, partyFrom, partyTo)
|
|
||||||
: this.partyRepository.findForInterestBatch(agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
|
|
||||||
for (final Party party : parties) {
|
|
||||||
if (!matchesSelectedBranch(party.getAgent().getHomeBranch(), officeNames)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final BigDecimal rate = party.getInterestRate();
|
|
||||||
final BigDecimal balance = party.getCurrentBalance();
|
|
||||||
if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0 || balance.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocalDate accrueFrom = party.getInterestDate() != null ? party.getInterestDate() : party.getOpeningDate();
|
|
||||||
final long days = ChronoUnit.DAYS.between(accrueFrom, jobDate);
|
|
||||||
if (days <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final BigDecimal amount = balance.multiply(rate).multiply(BigDecimal.valueOf(days))
|
|
||||||
.divide(HUNDRED.multiply(DAYS_IN_YEAR), 6, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
|
|
||||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
batchRun.addRow(new BatchRunRow(party.getId(), party.getAgent().getId(), amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateCommissionRows(final BatchRun batchRun, final Long agentFrom, final Long agentTo, final LocalDate jobDate,
|
|
||||||
final Set<String> officeNames) {
|
|
||||||
final List<Agent> agents = this.agentRepository.findBetweenIds(agentFrom, agentTo);
|
|
||||||
|
|
||||||
for (final Agent agent : agents) {
|
|
||||||
if (!matchesSelectedBranch(agent.getHomeBranch(), officeNames)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocalDate fromDate = agent.getLastCommissionDate() != null ? agent.getLastCommissionDate().plusDays(1)
|
|
||||||
: agent.getAgentOpeningDate();
|
|
||||||
if (fromDate.isAfter(jobDate)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final BigDecimal collectionVolume = this.partyTransactionRepository.sumPartyTranCredits(agent.getId(), fromDate, jobDate);
|
|
||||||
if (collectionVolume == null || collectionVolume.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<CommissionSlab> matchingSlabs = this.commissionSlabRepository.findMatchingSlabs("ACCOUNT", collectionVolume);
|
|
||||||
if (matchingSlabs.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final BigDecimal commissionPercent = matchingSlabs.get(0).getCommissionPercent();
|
|
||||||
final BigDecimal amount = collectionVolume.multiply(commissionPercent).divide(HUNDRED, 2, RoundingMode.HALF_UP);
|
|
||||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
batchRun.addRow(new BatchRunRow(null, agent.getId(), amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult postBatchRun(final Long batchRunId, final JsonCommand command) {
|
|
||||||
|
|
||||||
final BatchRun batchRun = this.batchRunRepository.findById(batchRunId).orElseThrow(() -> new BatchRunNotFoundException(batchRunId));
|
|
||||||
if (batchRun.isPosted()) {
|
|
||||||
throw new BatchRunAlreadyPostedException(batchRunId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean isCommissionJob = "COMMISSION_CALCULATION".equals(batchRun.getJobType());
|
|
||||||
final String transactionType = "OD_INTEREST_CALCULATION".equals(batchRun.getJobType()) ? "OD_INTEREST_POST" : "INTEREST_POST";
|
|
||||||
|
|
||||||
for (final BatchRunRow row : batchRun.getRows()) {
|
|
||||||
if (isCommissionJob) {
|
|
||||||
final Agent agent = this.agentRepository.findById(row.getAgentId())
|
|
||||||
.orElseThrow(() -> new AgentNotFoundException(row.getAgentId()));
|
|
||||||
agent.postCommission(row.getAmount(), batchRun.getJobDate());
|
|
||||||
} else {
|
|
||||||
final Party party = this.partyRepository.findById(row.getPartyId())
|
|
||||||
.orElseThrow(() -> new PartyNotFoundException(row.getPartyId()));
|
|
||||||
final Agent agent = party.getAgent();
|
|
||||||
final BigDecimal newBalance = party.credit(row.getAmount());
|
|
||||||
party.markInterestCalculatedThrough(batchRun.getJobDate());
|
|
||||||
|
|
||||||
final PartyTransaction transaction = new PartyTransaction(party, agent, batchRun.getJobDate(), transactionType,
|
|
||||||
"CREDIT", null, null, null, row.getAmount(), null, null, "DDS batch run #" + batchRun.getId(), newBalance);
|
|
||||||
this.partyTransactionRepository.save(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
batchRun.markPosted(LocalDate.now());
|
|
||||||
this.batchRunRepository.saveAndFlush(batchRun);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(batchRunId) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.batch.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.organisation.commissionslab.domain.CommissionSlabRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.domain.BatchRunRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.serialization.BatchRunCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunReadPlatformServiceImpl;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.batch.service.BatchRunWritePlatformServiceJpaRepositoryImpl;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class DdsBatchConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(BatchRunReadPlatformService.class)
|
|
||||||
public BatchRunReadPlatformService batchRunReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new BatchRunReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(BatchRunWritePlatformService.class)
|
|
||||||
public BatchRunWritePlatformService batchRunWritePlatformService(BatchRunCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
|
||||||
BatchRunRepository batchRunRepository, PartyRepository partyRepository, AgentRepository agentRepository,
|
|
||||||
PartyTransactionRepository partyTransactionRepository, CommissionSlabRepository commissionSlabRepository) {
|
|
||||||
return new BatchRunWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, batchRunRepository, partyRepository,
|
|
||||||
agentRepository, partyTransactionRepository, commissionSlabRepository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-128
@@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.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.GET;
|
|
||||||
import jakarta.ws.rs.POST;
|
|
||||||
import jakarta.ws.rs.PUT;
|
|
||||||
import jakarta.ws.rs.Path;
|
|
||||||
import jakarta.ws.rs.PathParam;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.QueryParam;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/parties")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Parties", description = "A DDS Party is a customer's daily-deposit scheme account, linked to the Agent who collects for it. Carries the scheme's KYC, terms, two-stage overdue flags and a nominee list.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartiesApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "PARTY";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final PartyReadPlatformService readPlatformService;
|
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve DDS Parties", description = "Returns the list of DDS parties.")
|
|
||||||
public Collection<PartyData> retrieveAll() {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{partyId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve a DDS Party", description = "Returns the details of a DDS party, including its nominees.")
|
|
||||||
public PartyData retrieveOne(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveOne(partyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Create a DDS Party", description = "Mandatory fields: clientId, agentId, subType, partyName, address1, "
|
|
||||||
+ "city, pincode, mobileNoSms, operation, adultMinor, openingDate, dailyDepositAmount, days\n" + "\n"
|
|
||||||
+ "Optional fields: lastAccount, nominal, aadharNo, partyName1, address2, address3, gender, panCardNo, telephone, "
|
|
||||||
+ "birthDate, occupation, risk, ckycId, minorDate, guardianClientId, minAccountOpt, relation, month, maturityDate, "
|
|
||||||
+ "reopenDate, depositAmount, ledgerNo, withdrawType, lien, payDate, paymentAmount, openingBalance, isSmsActive, "
|
|
||||||
+ "isEmailActive, interestDate, spDescription, freezeCode, advCode, creditToBranch, creditToAccountType, "
|
|
||||||
+ "creditToAccount, nomineeApplicable, nomineeSplitMode, nominees, savingsAccountNo (links this party to an existing, "
|
|
||||||
+ "non-closed real savings account of the same client, resolved by account number - omit if the client has none)")
|
|
||||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.createParty() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PUT
|
|
||||||
@Path("{partyId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Update a DDS Party", description = "Updates the details of a DDS party.")
|
|
||||||
public CommandProcessingResult update(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId,
|
|
||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.updateParty(partyId) //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("{partyId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Close a DDS Party", description = "command=close is the only supported command. Mandatory field: "
|
|
||||||
+ "closeDate. The party's balance must already be zero (settled with a full Withdraw Deposite payout) before it "
|
|
||||||
+ "can be closed.")
|
|
||||||
public CommandProcessingResult handleCommand(@PathParam("partyId") @Parameter(description = "partyId") final Long partyId,
|
|
||||||
@QueryParam("command") @Parameter(description = "command") final String commandParam,
|
|
||||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
if (!"close".equals(commandParam)) {
|
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
|
||||||
}
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.closeParty(partyId) //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.domain;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface PartyRepository extends JpaRepository<Party, Long> {
|
|
||||||
|
|
||||||
@Query("select coalesce(max(cast(substring(p.partyCode, 3) as long)), 0) from Party p where p.partyCode like 'PT%'")
|
|
||||||
Long retrieveMaxPartyCodeSuffix();
|
|
||||||
|
|
||||||
Optional<Party> findByPartyCode(String partyCode);
|
|
||||||
|
|
||||||
@Query("select p from Party p where p.agent.id between :agentFrom and :agentTo and p.id between :partyFrom and :partyTo")
|
|
||||||
List<Party> findForInterestBatch(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo,
|
|
||||||
@Param("partyFrom") Long partyFrom, @Param("partyTo") Long partyTo);
|
|
||||||
|
|
||||||
@Query("select p from Party p where p.agent.id between :agentFrom and :agentTo and p.id between :partyFrom and :partyTo "
|
|
||||||
+ "and (p.od1Flag = true or p.od2Flag = true)")
|
|
||||||
List<Party> findForOdInterestBatch(@Param("agentFrom") Long agentFrom, @Param("agentTo") Long agentTo,
|
|
||||||
@Param("partyFrom") Long partyFrom, @Param("partyTo") Long partyTo);
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "PARTY", action = "CLOSE")
|
|
||||||
public class ClosePartyCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final PartyWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ClosePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.closeParty(command.entityId(), command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "PARTY", action = "CREATE")
|
|
||||||
public class CreatePartyCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final PartyWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CreatePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.createParty(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "PARTY", action = "UPDATE")
|
|
||||||
public class UpdatePartyCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final PartyWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public UpdatePartyCommandHandler(final PartyWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.updateParty(command.entityId(), command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-294
@@ -1,294 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.serialization;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public final class PartyCommandFromApiJsonDeserializer {
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("clientId", "agentId", "subType",
|
|
||||||
"lastAccount", "nominal", "aadharNo", "partyName", "partyName1", "address1", "address2", "address3", "city", "pincode",
|
|
||||||
"gender", "panCardNo", "telephone", "mobileNoSms", "operation", "birthDate", "occupation", "risk", "ckycId", "adultMinor",
|
|
||||||
"minorDate", "guardianClientId", "minAccountOpt", "relation", "openingDate", "month", "maturityDate", "reopenDate",
|
|
||||||
"depositAmount", "dailyDepositAmount", "days", "ledgerNo", "interestRate", "withdrawType", "lien", "payDate",
|
|
||||||
"paymentAmount", "openingBalance", "isSmsActive", "isEmailActive", "interestDate", "spDescription", "freezeCode", "advCode",
|
|
||||||
"creditToBranch", "creditToAccountType", "creditToAccount", "nomineeApplicable", "nomineeSplitMode", "nominees",
|
|
||||||
"savingsAccountNo", "dateFormat", "locale"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_NOMINEE_PARAMETERS = new HashSet<>(
|
|
||||||
Arrays.asList("nominationDate", "name", "mobileNo", "birthDate", "minor", "relation", "gender", "guardianName"));
|
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public PartyCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForCreate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
v.reset().parameter("clientId").value(this.fromApiJsonHelper.extractLongNamed("clientId", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
v.reset().parameter("subType").value(this.fromApiJsonHelper.extractStringNamed("subType", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(60);
|
|
||||||
v.reset().parameter("partyName").value(this.fromApiJsonHelper.extractStringNamed("partyName", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
v.reset().parameter("address1").value(this.fromApiJsonHelper.extractStringNamed("address1", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(200);
|
|
||||||
v.reset().parameter("city").value(this.fromApiJsonHelper.extractStringNamed("city", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
v.reset().parameter("pincode").value(this.fromApiJsonHelper.extractStringNamed("pincode", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
v.reset().parameter("mobileNoSms").value(this.fromApiJsonHelper.extractStringNamed("mobileNoSms", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
v.reset().parameter("operation").value(this.fromApiJsonHelper.extractStringNamed("operation", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
v.reset().parameter("adultMinor").value(this.fromApiJsonHelper.extractStringNamed("adultMinor", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
v.reset().parameter("openingDate").value(this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element)).notNull();
|
|
||||||
v.reset().parameter("dailyDepositAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("dailyDepositAmount", element))
|
|
||||||
.notNull().positiveAmount();
|
|
||||||
v.reset().parameter("days").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("days", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
|
|
||||||
validateOptionalFields(element, v);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("nominees", element)) {
|
|
||||||
validateNominees(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForUpdate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("clientId", element)) {
|
|
||||||
v.reset().parameter("clientId").value(this.fromApiJsonHelper.extractLongNamed("clientId", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("agentId", element)) {
|
|
||||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("subType", element)) {
|
|
||||||
v.reset().parameter("subType").value(this.fromApiJsonHelper.extractStringNamed("subType", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(60);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("partyName", element)) {
|
|
||||||
v.reset().parameter("partyName").value(this.fromApiJsonHelper.extractStringNamed("partyName", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("address1", element)) {
|
|
||||||
v.reset().parameter("address1").value(this.fromApiJsonHelper.extractStringNamed("address1", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(200);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("city", element)) {
|
|
||||||
v.reset().parameter("city").value(this.fromApiJsonHelper.extractStringNamed("city", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(100);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("pincode", element)) {
|
|
||||||
v.reset().parameter("pincode").value(this.fromApiJsonHelper.extractStringNamed("pincode", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("mobileNoSms", element)) {
|
|
||||||
v.reset().parameter("mobileNoSms").value(this.fromApiJsonHelper.extractStringNamed("mobileNoSms", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("operation", element)) {
|
|
||||||
v.reset().parameter("operation").value(this.fromApiJsonHelper.extractStringNamed("operation", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("adultMinor", element)) {
|
|
||||||
v.reset().parameter("adultMinor").value(this.fromApiJsonHelper.extractStringNamed("adultMinor", element)).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("openingDate", element)) {
|
|
||||||
v.reset().parameter("openingDate").value(this.fromApiJsonHelper.extractLocalDateNamed("openingDate", element)).notNull();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("dailyDepositAmount", element)) {
|
|
||||||
v.reset().parameter("dailyDepositAmount")
|
|
||||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("dailyDepositAmount", element)).notNull()
|
|
||||||
.positiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("days", element)) {
|
|
||||||
v.reset().parameter("days").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("days", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
validateOptionalFields(element, v);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("nominees", element)) {
|
|
||||||
validateNominees(element.getAsJsonObject(), dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForClose(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, Set.of("closeDate", "dateFormat", "locale"));
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
v.reset().parameter("closeDate").value(this.fromApiJsonHelper.extractLocalDateNamed("closeDate", element)).notNull();
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateOptionalFields(final JsonElement element, final DataValidatorBuilder v) {
|
|
||||||
optionalString(element, v, "lastAccount", 50);
|
|
||||||
optionalString(element, v, "aadharNo", 20);
|
|
||||||
optionalString(element, v, "partyName1", 100);
|
|
||||||
optionalString(element, v, "address2", 200);
|
|
||||||
optionalString(element, v, "address3", 200);
|
|
||||||
optionalString(element, v, "gender", 20);
|
|
||||||
optionalString(element, v, "panCardNo", 20);
|
|
||||||
optionalString(element, v, "telephone", 20);
|
|
||||||
optionalString(element, v, "occupation", 40);
|
|
||||||
optionalString(element, v, "risk", 20);
|
|
||||||
optionalString(element, v, "ckycId", 40);
|
|
||||||
optionalString(element, v, "minAccountOpt", 40);
|
|
||||||
optionalString(element, v, "relation", 40);
|
|
||||||
optionalString(element, v, "ledgerNo", 40);
|
|
||||||
optionalString(element, v, "withdrawType", 20);
|
|
||||||
optionalString(element, v, "spDescription", 500);
|
|
||||||
optionalString(element, v, "freezeCode", 20);
|
|
||||||
optionalString(element, v, "advCode", 40);
|
|
||||||
optionalString(element, v, "creditToBranch", 100);
|
|
||||||
optionalString(element, v, "creditToAccountType", 100);
|
|
||||||
optionalString(element, v, "creditToAccount", 50);
|
|
||||||
optionalString(element, v, "nomineeSplitMode", 20);
|
|
||||||
optionalString(element, v, "savingsAccountNo", 20);
|
|
||||||
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("guardianClientId", element)) {
|
|
||||||
v.reset().parameter("guardianClientId").value(this.fromApiJsonHelper.extractLongNamed("guardianClientId", element))
|
|
||||||
.ignoreIfNull().integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("month", element)) {
|
|
||||||
v.reset().parameter("month").value(this.fromApiJsonHelper.extractIntegerSansLocaleNamed("month", element)).ignoreIfNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("depositAmount", element)) {
|
|
||||||
v.reset().parameter("depositAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("depositAmount", element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("paymentAmount", element)) {
|
|
||||||
v.reset().parameter("paymentAmount")
|
|
||||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("paymentAmount", element)).ignoreIfNull()
|
|
||||||
.zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("openingBalance", element)) {
|
|
||||||
v.reset().parameter("openingBalance")
|
|
||||||
.value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("openingBalance", element)).ignoreIfNull()
|
|
||||||
.zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
if (this.fromApiJsonHelper.parameterExists("interestRate", element)) {
|
|
||||||
v.reset().parameter("interestRate").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRate", element))
|
|
||||||
.ignoreIfNull().zeroOrPositiveAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void optionalString(final JsonElement element, final DataValidatorBuilder v, final String paramName, final int maxLength) {
|
|
||||||
if (this.fromApiJsonHelper.parameterExists(paramName, element)) {
|
|
||||||
v.reset().parameter(paramName).value(this.fromApiJsonHelper.extractStringNamed(paramName, element)).ignoreIfNull()
|
|
||||||
.notExceedingLengthOf(maxLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateNominees(final JsonObject topLevelJson, final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("party.nominee");
|
|
||||||
final var nomineesArray = topLevelJson.getAsJsonArray("nominees");
|
|
||||||
for (int i = 0; i < nomineesArray.size(); i++) {
|
|
||||||
final JsonObject nomineeJson = nomineesArray.get(i).getAsJsonObject();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(nomineeJson, SUPPORTED_NOMINEE_PARAMETERS);
|
|
||||||
|
|
||||||
v.reset().parameterAtIndexArray("nominationDate", i).value(extractString(nomineeJson, "nominationDate")).notBlank();
|
|
||||||
v.reset().parameterAtIndexArray("name", i).value(extractString(nomineeJson, "name")).notBlank().notExceedingLengthOf(100);
|
|
||||||
v.reset().parameterAtIndexArray("relation", i).value(extractString(nomineeJson, "relation")).notBlank()
|
|
||||||
.notExceedingLengthOf(40);
|
|
||||||
v.reset().parameterAtIndexArray("gender", i).value(extractString(nomineeJson, "gender")).notBlank()
|
|
||||||
.notExceedingLengthOf(20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractString(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
|
||||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
|
||||||
dataValidationErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
|
||||||
|
|
||||||
public interface PartyReadPlatformService {
|
|
||||||
|
|
||||||
Collection<PartyData> retrieveAll();
|
|
||||||
|
|
||||||
PartyData retrieveOne(Long partyId);
|
|
||||||
}
|
|
||||||
-199
@@ -1,199 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.service;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.data.PartyData;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.data.PartyNomineeData;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartyReadPlatformServiceImpl implements PartyReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
private static final PartyMapper PARTY_MAPPER = new PartyMapper();
|
|
||||||
private static final PartyNomineeMapper NOMINEE_MAPPER = new PartyNomineeMapper();
|
|
||||||
|
|
||||||
private static final class PartyMapper implements RowMapper<PartyData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " p.id as id, p.client_id as clientId, c.display_name as clientName, p.agent_id as agentId, "
|
|
||||||
+ " a.agent_code as agentCode, a.agent_name as agentName, p.savings_account_id as savingsAccountId, "
|
|
||||||
+ " p.sub_type as subType, p.party_code as partyCode, p.last_account as lastAccount, p.nominal as nominal, "
|
|
||||||
+ " p.aadhar_no as aadharNo, p.party_name as partyName, p.party_name1 as partyName1, p.address1 as address1, "
|
|
||||||
+ " p.address2 as address2, p.address3 as address3, p.city as city, p.pincode as pincode, p.gender as gender, "
|
|
||||||
+ " p.pan_card_no as panCardNo, p.telephone as telephone, p.mobile_no_sms as mobileNoSms, p.operation as operation, "
|
|
||||||
+ " p.birth_date as birthDate, p.occupation as occupation, p.risk as risk, p.ckyc_id as ckycId, "
|
|
||||||
+ " p.adult_minor as adultMinor, p.minor_date as minorDate, p.guardian_client_id as guardianClientId, "
|
|
||||||
+ " p.min_account_opt as minAccountOpt, p.relation as relation, p.opening_date as openingDate, "
|
|
||||||
+ " p.months as month, p.maturity_date as maturityDate, p.reopen_date as reopenDate, "
|
|
||||||
+ " p.deposit_amount as depositAmount, p.daily_deposit_amount as dailyDepositAmount, p.days as days, "
|
|
||||||
+ " p.ledger_no as ledgerNo, p.maturity_amount as maturityAmount, p.interest_rate as interestRate, "
|
|
||||||
+ " p.withdraw_type as withdrawType, p.od1_flag as od1Flag, p.od2_flag as od2Flag, p.lien as lien, "
|
|
||||||
+ " p.pay_date as payDate, p.payment_amount as paymentAmount, p.current_balance as currentBalance, "
|
|
||||||
+ " p.opening_balance as openingBalance, p.is_sms_active as isSmsActive, p.is_email_active as isEmailActive, "
|
|
||||||
+ " p.interest_calc as interestCalc, p.interest_date as interestDate, p.sp_description as spDescription, "
|
|
||||||
+ " p.freeze_code as freezeCode, p.adv_code as advCode, p.credit_to_branch as creditToBranch, "
|
|
||||||
+ " p.credit_to_account_type as creditToAccountType, p.credit_to_account as creditToAccount, "
|
|
||||||
+ " p.nominee_applicable as nomineeApplicable, p.nominee_split_mode as nomineeSplitMode, "
|
|
||||||
+ " p.status as status, p.close_date as closeDate "
|
|
||||||
+ " from m_dds_party p join m_client c on c.id = p.client_id join m_dds_agent a on a.id = p.agent_id";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartyData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
return PartyData.builder() //
|
|
||||||
.id(rs.getLong("id")) //
|
|
||||||
.clientId(rs.getLong("clientId")) //
|
|
||||||
.clientName(rs.getString("clientName")) //
|
|
||||||
.agentId(rs.getLong("agentId")) //
|
|
||||||
.agentCode(rs.getString("agentCode")) //
|
|
||||||
.agentName(rs.getString("agentName")) //
|
|
||||||
.savingsAccountId(JdbcSupport.getLong(rs, "savingsAccountId")) //
|
|
||||||
.subType(rs.getString("subType")) //
|
|
||||||
.partyCode(rs.getString("partyCode")) //
|
|
||||||
.lastAccount(rs.getString("lastAccount")) //
|
|
||||||
.nominal(rs.getBoolean("nominal")) //
|
|
||||||
.aadharNo(rs.getString("aadharNo")) //
|
|
||||||
.partyName(rs.getString("partyName")) //
|
|
||||||
.partyName1(rs.getString("partyName1")) //
|
|
||||||
.address1(rs.getString("address1")) //
|
|
||||||
.address2(rs.getString("address2")) //
|
|
||||||
.address3(rs.getString("address3")) //
|
|
||||||
.city(rs.getString("city")) //
|
|
||||||
.pincode(rs.getString("pincode")) //
|
|
||||||
.gender(rs.getString("gender")) //
|
|
||||||
.panCardNo(rs.getString("panCardNo")) //
|
|
||||||
.telephone(rs.getString("telephone")) //
|
|
||||||
.mobileNoSms(rs.getString("mobileNoSms")) //
|
|
||||||
.operation(rs.getString("operation")) //
|
|
||||||
.birthDate(JdbcSupport.getLocalDate(rs, "birthDate")) //
|
|
||||||
.occupation(rs.getString("occupation")) //
|
|
||||||
.risk(rs.getString("risk")) //
|
|
||||||
.ckycId(rs.getString("ckycId")) //
|
|
||||||
.adultMinor(rs.getString("adultMinor")) //
|
|
||||||
.minorDate(JdbcSupport.getLocalDate(rs, "minorDate")) //
|
|
||||||
.guardianClientId(JdbcSupport.getLong(rs, "guardianClientId")) //
|
|
||||||
.minAccountOpt(rs.getString("minAccountOpt")) //
|
|
||||||
.relation(rs.getString("relation")) //
|
|
||||||
.openingDate(JdbcSupport.getLocalDate(rs, "openingDate")) //
|
|
||||||
.month(JdbcSupport.getInteger(rs, "month")) //
|
|
||||||
.maturityDate(JdbcSupport.getLocalDate(rs, "maturityDate")) //
|
|
||||||
.reopenDate(JdbcSupport.getLocalDate(rs, "reopenDate")) //
|
|
||||||
.depositAmount(rs.getBigDecimal("depositAmount")) //
|
|
||||||
.dailyDepositAmount(rs.getBigDecimal("dailyDepositAmount")) //
|
|
||||||
.days(JdbcSupport.getInteger(rs, "days")) //
|
|
||||||
.ledgerNo(rs.getString("ledgerNo")) //
|
|
||||||
.maturityAmount(rs.getBigDecimal("maturityAmount")) //
|
|
||||||
.interestRate(rs.getBigDecimal("interestRate")) //
|
|
||||||
.withdrawType(rs.getString("withdrawType")) //
|
|
||||||
.od1Flag(rs.getBoolean("od1Flag")) //
|
|
||||||
.od2Flag(rs.getBoolean("od2Flag")) //
|
|
||||||
.lien(rs.getBoolean("lien")) //
|
|
||||||
.payDate(JdbcSupport.getLocalDate(rs, "payDate")) //
|
|
||||||
.paymentAmount(rs.getBigDecimal("paymentAmount")) //
|
|
||||||
.currentBalance(rs.getBigDecimal("currentBalance")) //
|
|
||||||
.openingBalance(rs.getBigDecimal("openingBalance")) //
|
|
||||||
.isSmsActive(rs.getBoolean("isSmsActive")) //
|
|
||||||
.isEmailActive(rs.getBoolean("isEmailActive")) //
|
|
||||||
.interestCalc(rs.getBigDecimal("interestCalc")) //
|
|
||||||
.interestDate(JdbcSupport.getLocalDate(rs, "interestDate")) //
|
|
||||||
.spDescription(rs.getString("spDescription")) //
|
|
||||||
.freezeCode(rs.getString("freezeCode")) //
|
|
||||||
.advCode(rs.getString("advCode")) //
|
|
||||||
.creditToBranch(rs.getString("creditToBranch")) //
|
|
||||||
.creditToAccountType(rs.getString("creditToAccountType")) //
|
|
||||||
.creditToAccount(rs.getString("creditToAccount")) //
|
|
||||||
.nomineeApplicable(rs.getBoolean("nomineeApplicable")) //
|
|
||||||
.nomineeSplitMode(rs.getString("nomineeSplitMode")) //
|
|
||||||
.status(rs.getString("status")) //
|
|
||||||
.closeDate(JdbcSupport.getLocalDate(rs, "closeDate")) //
|
|
||||||
.nominees(new ArrayList<>()) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PartyNomineeMapper implements RowMapper<Map.Entry<Long, PartyNomineeData>> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " n.party_id as partyId, n.id as id, n.nomination_date as nominationDate, n.name as name, "
|
|
||||||
+ " n.mobile_no as mobileNo, n.birth_date as birthDate, n.minor as minor, n.relation as relation, "
|
|
||||||
+ " n.gender as gender, n.guardian_name as guardianName from m_dds_party_nominee n";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map.Entry<Long, PartyNomineeData> mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
final Long partyId = rs.getLong("partyId");
|
|
||||||
final PartyNomineeData data = PartyNomineeData.instance(rs.getLong("id"), JdbcSupport.getLocalDate(rs, "nominationDate"),
|
|
||||||
rs.getString("name"), rs.getString("mobileNo"), JdbcSupport.getLocalDate(rs, "birthDate"), rs.getBoolean("minor"),
|
|
||||||
rs.getString("relation"), rs.getString("gender"), rs.getString("guardianName"));
|
|
||||||
return Map.entry(partyId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<PartyData> retrieveAll() {
|
|
||||||
final String sql = "select " + PARTY_MAPPER.schema() + " order by p.id";
|
|
||||||
final List<PartyData> parties = this.jdbcTemplate.query(sql, PARTY_MAPPER); // NOSONAR
|
|
||||||
return attachNominees(parties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartyData retrieveOne(final Long partyId) {
|
|
||||||
try {
|
|
||||||
final String sql = "select " + PARTY_MAPPER.schema() + " where p.id = ?";
|
|
||||||
final PartyData party = this.jdbcTemplate.queryForObject(sql, PARTY_MAPPER, partyId); // NOSONAR
|
|
||||||
return attachNominees(List.of(party)).iterator().next();
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
throw new PartyNotFoundException(partyId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<PartyData> attachNominees(final List<PartyData> parties) {
|
|
||||||
if (parties.isEmpty()) {
|
|
||||||
return parties;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String sql = "select " + NOMINEE_MAPPER.schema() + " order by n.id";
|
|
||||||
final List<Map.Entry<Long, PartyNomineeData>> rows = this.jdbcTemplate.query(sql, NOMINEE_MAPPER); // NOSONAR
|
|
||||||
|
|
||||||
final Map<Long, List<PartyNomineeData>> nomineesByPartyId = new LinkedHashMap<>();
|
|
||||||
for (final Map.Entry<Long, PartyNomineeData> row : rows) {
|
|
||||||
nomineesByPartyId.computeIfAbsent(row.getKey(), k -> new ArrayList<>()).add(row.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<PartyData> result = new ArrayList<>(parties.size());
|
|
||||||
for (final PartyData p : parties) {
|
|
||||||
result.add(p.toBuilder().nominees(nomineesByPartyId.getOrDefault(p.getId(), new ArrayList<>())).build());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.service;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
|
|
||||||
public interface PartyWritePlatformService {
|
|
||||||
|
|
||||||
CommandProcessingResult createParty(JsonCommand command);
|
|
||||||
|
|
||||||
CommandProcessingResult updateParty(Long partyId, JsonCommand command);
|
|
||||||
|
|
||||||
CommandProcessingResult closeParty(Long partyId, JsonCommand command);
|
|
||||||
}
|
|
||||||
-235
@@ -1,235 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.service;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import jakarta.persistence.PersistenceException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyNominee;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyAlreadyClosedException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyHasBalanceException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartySavingsAccountClientMismatchException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartySavingsAccountNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.serialization.PartyCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartyWritePlatformServiceJpaRepositoryImpl implements PartyWritePlatformService {
|
|
||||||
|
|
||||||
private final PartyCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
|
||||||
private final PartyRepository partyRepository;
|
|
||||||
private final AgentRepository agentRepository;
|
|
||||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
|
||||||
private final SavingsAccountRepository savingsAccountRepository;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult createParty(final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
|
||||||
|
|
||||||
final Long clientId = command.longValueOfParameterNamed("clientId");
|
|
||||||
final Client client = this.clientRepositoryWrapper.findOneWithNotFoundDetection(clientId);
|
|
||||||
|
|
||||||
final Long agentId = command.longValueOfParameterNamed("agentId");
|
|
||||||
final Agent agent = this.agentRepository.findById(agentId)
|
|
||||||
.orElseThrow(() -> new AgentNotFoundException(agentId));
|
|
||||||
|
|
||||||
final String partyCode = generateNextPartyCode();
|
|
||||||
final Party party = Party.fromJson(client, agent, partyCode, command);
|
|
||||||
|
|
||||||
linkSavingsAccountIfProvided(party, clientId, command);
|
|
||||||
addNomineesFromCommand(party, command);
|
|
||||||
|
|
||||||
this.partyRepository.saveAndFlush(party);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(party.getId()) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
|
||||||
handleDataIntegrityIssues(throwable, dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult updateParty(final Long partyId, final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForUpdate(command.json());
|
|
||||||
|
|
||||||
final Party partyForUpdate = this.partyRepository.findById(partyId).orElseThrow(() -> new PartyNotFoundException(partyId));
|
|
||||||
final Map<String, Object> changesOnly = partyForUpdate.update(command);
|
|
||||||
|
|
||||||
if (changesOnly.containsKey("clientId")) {
|
|
||||||
final Long newClientId = (Long) changesOnly.get("clientId");
|
|
||||||
final Client newClient = this.clientRepositoryWrapper.findOneWithNotFoundDetection(newClientId);
|
|
||||||
partyForUpdate.changeClient(newClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changesOnly.containsKey("agentId")) {
|
|
||||||
final Long newAgentId = (Long) changesOnly.get("agentId");
|
|
||||||
final Agent newAgent = this.agentRepository.findById(newAgentId)
|
|
||||||
.orElseThrow(() -> new AgentNotFoundException(newAgentId));
|
|
||||||
partyForUpdate.changeAgent(newAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.parameterExists("nominees")) {
|
|
||||||
partyForUpdate.resetNominees();
|
|
||||||
addNomineesFromCommand(partyForUpdate, command);
|
|
||||||
changesOnly.put("nominees", command.jsonFragment("nominees"));
|
|
||||||
}
|
|
||||||
|
|
||||||
linkSavingsAccountIfProvided(partyForUpdate, partyForUpdate.clientId(), command);
|
|
||||||
|
|
||||||
this.partyRepository.saveAndFlush(partyForUpdate);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(partyId).with(changesOnly)
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
|
||||||
handleDataIntegrityIssues(throwable, dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult closeParty(final Long partyId, final JsonCommand command) {
|
|
||||||
|
|
||||||
this.fromApiJsonDeserializer.validateForClose(command.json());
|
|
||||||
|
|
||||||
final Party party = this.partyRepository.findById(partyId).orElseThrow(() -> new PartyNotFoundException(partyId));
|
|
||||||
|
|
||||||
if (party.isClosed()) {
|
|
||||||
throw new PartyAlreadyClosedException(party.getPartyCode());
|
|
||||||
}
|
|
||||||
if (party.getCurrentBalance().compareTo(BigDecimal.ZERO) != 0) {
|
|
||||||
throw new PartyHasBalanceException(party.getPartyCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
|
|
||||||
party.close(closeDate);
|
|
||||||
this.partyRepository.saveAndFlush(party);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(partyId).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the caller gave a savingsAccountNo, links this Party to that real Savings Account - resolved
|
|
||||||
* by account number since that's what an officer actually has to hand, not the internal id. Left
|
|
||||||
* unlinked (as before) when the parameter isn't given: a Party doesn't require a real account,
|
|
||||||
* this is opportunistic linking for a client that already happens to have one.
|
|
||||||
*/
|
|
||||||
private void linkSavingsAccountIfProvided(final Party party, final Long expectedClientId, final JsonCommand command) {
|
|
||||||
if (!command.parameterExists("savingsAccountNo")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String savingsAccountNo = command.stringValueOfParameterNamed("savingsAccountNo");
|
|
||||||
final SavingsAccount savingsAccount = this.savingsAccountRepository.findNonClosedAccountByAccountNumber(savingsAccountNo);
|
|
||||||
if (savingsAccount == null) {
|
|
||||||
throw new PartySavingsAccountNotFoundException(savingsAccountNo);
|
|
||||||
}
|
|
||||||
if (!expectedClientId.equals(savingsAccount.clientId())) {
|
|
||||||
throw new PartySavingsAccountClientMismatchException(savingsAccountNo);
|
|
||||||
}
|
|
||||||
party.linkSavingsAccount(savingsAccount.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNomineesFromCommand(final Party party, final JsonCommand command) {
|
|
||||||
if (!command.parameterExists("nominees")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final JsonArray nomineesArray = command.arrayOfParameterNamed("nominees");
|
|
||||||
if (nomineesArray == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < nomineesArray.size(); i++) {
|
|
||||||
final JsonObject nomineeJson = nomineesArray.get(i).getAsJsonObject();
|
|
||||||
final LocalDate nominationDate = LocalDate.parse(nomineeJson.get("nominationDate").getAsString());
|
|
||||||
final String name = nomineeJson.get("name").getAsString();
|
|
||||||
final String mobileNo = nomineeJson.has("mobileNo") && !nomineeJson.get("mobileNo").isJsonNull()
|
|
||||||
? nomineeJson.get("mobileNo").getAsString()
|
|
||||||
: null;
|
|
||||||
final LocalDate birthDate = nomineeJson.has("birthDate") && !nomineeJson.get("birthDate").isJsonNull()
|
|
||||||
? LocalDate.parse(nomineeJson.get("birthDate").getAsString())
|
|
||||||
: null;
|
|
||||||
final boolean minor = nomineeJson.has("minor") && !nomineeJson.get("minor").isJsonNull() && nomineeJson.get("minor")
|
|
||||||
.getAsBoolean();
|
|
||||||
final String relation = nomineeJson.get("relation").getAsString();
|
|
||||||
final String gender = nomineeJson.get("gender").getAsString();
|
|
||||||
final String guardianName = nomineeJson.has("guardianName") && !nomineeJson.get("guardianName").isJsonNull()
|
|
||||||
? nomineeJson.get("guardianName").getAsString()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
party.addNominee(new PartyNominee(nominationDate, name, mobileNo, birthDate, minor, relation, gender, guardianName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateNextPartyCode() {
|
|
||||||
final Long maxSuffix = this.partyRepository.retrieveMaxPartyCodeSuffix();
|
|
||||||
final long nextSuffix = (maxSuffix == null ? 0L : maxSuffix) + 1L;
|
|
||||||
return String.format("PT%04d", nextSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
|
||||||
if (realCause.getMessage() != null && realCause.getMessage().contains("party_code")) {
|
|
||||||
throw new PlatformDataIntegrityException("error.msg.party.duplicate.partyCode",
|
|
||||||
"A party with the generated code already exists, please retry");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("Error occured.", dve);
|
|
||||||
throw ErrorHandler.getMappable(dve, "error.msg.party.unknown.data.integrity.issue",
|
|
||||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.party.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.serialization.PartyCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyReadPlatformServiceImpl;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.service.PartyWritePlatformServiceJpaRepositoryImpl;
|
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class DdsPartyConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(PartyReadPlatformService.class)
|
|
||||||
public PartyReadPlatformService partyReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new PartyReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(PartyWritePlatformService.class)
|
|
||||||
public PartyWritePlatformService partyWritePlatformService(PartyCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
|
||||||
PartyRepository partyRepository, AgentRepository agentRepository, ClientRepositoryWrapper clientRepositoryWrapper,
|
|
||||||
SavingsAccountRepository savingsAccountRepository) {
|
|
||||||
return new PartyWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, partyRepository, agentRepository,
|
|
||||||
clientRepositoryWrapper, savingsAccountRepository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.report.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.Path;
|
|
||||||
import jakarta.ws.rs.PathParam;
|
|
||||||
import jakarta.ws.rs.Produces;
|
|
||||||
import jakarta.ws.rs.QueryParam;
|
|
||||||
import jakarta.ws.rs.core.MediaType;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
|
||||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/reports/{reportKey}")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Reports", description = "The DDS reports suite - one shared query engine behind a generic column/row shape, filtered by date range and agent/party id range.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DdsReportsApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "DDSREPORT";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final DdsReportReadPlatformService readPlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Run a DDS Report", description = "reportKey is one of: AGENT_CHECK_LIST, PARTY_CHECK_LIST, PARTY_BALANCE, "
|
|
||||||
+ "AGENT_WISE_BALANCE, AGENT_WISE_MONTHLY_COLLECTION, COMMISSION_REGISTER, COLLECTION_SUMMARY_AGENT_WISE, "
|
|
||||||
+ "AGENT_PARTYWISE_DAILY_COLLECTION, DAILY_COLLECTION_REPORT, PARTY_INTEREST_REGISTER, BALANCE_SUMMARY, "
|
|
||||||
+ "TRANSACTION_REGISTER, MATURE_DATE_WISE, INTEREST_CERTIFICATE, AGENT_COMMISSION_STATEMENT, SUPPLEMENTARY_DDS, "
|
|
||||||
+ "CLOSE_PARTY_AC_REGISTER, MASTER_DATA_EXPORT (the last three always return available=false - no matching data exists "
|
|
||||||
+ "in this system yet). "
|
|
||||||
+ "fromDate/toDate are ISO (yyyy-MM-dd); agentFrom/To and partyFrom/To default to the full id range.")
|
|
||||||
public DdsReportData runReport(@PathParam("reportKey") @Parameter(description = "reportKey") final String reportKey,
|
|
||||||
@QueryParam("fromDate") @Parameter(description = "fromDate") final String fromDate,
|
|
||||||
@QueryParam("toDate") @Parameter(description = "toDate") final String toDate,
|
|
||||||
@DefaultValue("1") @QueryParam("agentFrom") @Parameter(description = "agentFrom") final Long agentFrom,
|
|
||||||
@DefaultValue("9223372036854775807") @QueryParam("agentTo") @Parameter(description = "agentTo") final Long agentTo,
|
|
||||||
@DefaultValue("1") @QueryParam("partyFrom") @Parameter(description = "partyFrom") final Long partyFrom,
|
|
||||||
@DefaultValue("9223372036854775807") @QueryParam("partyTo") @Parameter(description = "partyTo") final Long partyTo) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
|
|
||||||
final LocalDate from = fromDate != null ? LocalDate.parse(fromDate) : LocalDate.of(1900, 1, 1);
|
|
||||||
final LocalDate to = toDate != null ? LocalDate.parse(toDate) : LocalDate.of(9999, 12, 31);
|
|
||||||
|
|
||||||
return readPlatformService.runReport(reportKey, from, to, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.report.service;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
|
||||||
|
|
||||||
public interface DdsReportReadPlatformService {
|
|
||||||
|
|
||||||
DdsReportData runReport(String reportKey, LocalDate fromDate, LocalDate toDate, Long agentFrom, Long agentTo, Long partyFrom,
|
|
||||||
Long partyTo);
|
|
||||||
}
|
|
||||||
-313
@@ -1,313 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.report.service;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
|
||||||
import org.apache.fineract.portfolio.dds.report.data.DdsReportData;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One switch over every DDS report key. Each branch is a real query against data this system
|
|
||||||
* actually has (Agent/Party/PartyTransaction/CommissionSlab/BatchRun).
|
|
||||||
*
|
|
||||||
* Since SubAgent was folded into Agent (2026-08-24, Agent now does the daily collection work
|
|
||||||
* directly), the queries here dropped a join level wherever they used to go Party/Transaction ->
|
|
||||||
* SubAgent -> Agent - they now join straight to Agent.
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DdsReportReadPlatformServiceImpl implements DdsReportReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DdsReportData runReport(final String reportKey, final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
|
|
||||||
return switch (reportKey) {
|
|
||||||
case "AGENT_CHECK_LIST" -> agentCheckList();
|
|
||||||
case "PARTY_CHECK_LIST", "PARTY_BALANCE" -> partyList(agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "AGENT_WISE_BALANCE" -> agentWiseBalance(agentFrom, agentTo);
|
|
||||||
case "AGENT_WISE_MONTHLY_COLLECTION" -> agentWiseCollection(fromDate, toDate, agentFrom, agentTo);
|
|
||||||
case "COMMISSION_REGISTER" -> commissionRegister(fromDate, toDate, agentFrom, agentTo);
|
|
||||||
case "COLLECTION_SUMMARY_AGENT_WISE" -> collectionSummaryAgentWise(fromDate, toDate, agentFrom, agentTo);
|
|
||||||
case "AGENT_PARTYWISE_DAILY_COLLECTION" -> agentPartywiseDailyCollection(fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
case "DAILY_COLLECTION_REPORT" -> dailyCollectionReport(fromDate, toDate, agentFrom, agentTo);
|
|
||||||
case "PARTY_INTEREST_REGISTER" -> partyInterestRegister(fromDate, toDate, partyFrom, partyTo);
|
|
||||||
case "BALANCE_SUMMARY" -> balanceSummary(agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "TRANSACTION_REGISTER" -> transactionRegister(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "MATURE_DATE_WISE" -> matureDateWise(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "INTEREST_CERTIFICATE" -> interestCertificate(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "AGENT_COMMISSION_STATEMENT" -> agentCommissionStatement(fromDate, toDate);
|
|
||||||
case "SUPPLEMENTARY_DDS" -> supplementaryDds(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "CLOSE_PARTY_AC_REGISTER" -> closePartyAcRegister(fromDate, toDate, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
case "MASTER_DATA_EXPORT" -> masterDataExport(agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
default -> throw new UnrecognizedQueryParamException("reportKey", reportKey);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData agentCheckList() {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", a.agent_city as \"City\", "
|
|
||||||
+ "a.agent_current_balance as \"Current Balance\" from m_dds_agent a order by a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql);
|
|
||||||
return available("AGENT_CHECK_LIST", List.of("Agent Code", "Agent Name", "City", "Current Balance"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData partyList(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
|
||||||
+ "a.agent_code as \"Agent Code\", p.current_balance as \"Current Balance\", "
|
|
||||||
+ "p.opening_date as \"Opening Date\" from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
|
||||||
+ "where a.id between ? and ? and p.id between ? and ? order by p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
return available("PARTY_CHECK_LIST", List.of("Party Code", "Party Name", "Agent Code", "Current Balance", "Opening Date"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData agentWiseBalance(final Long agentFrom, final Long agentTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", "
|
|
||||||
+ "a.agent_current_balance as \"Current Balance\" from m_dds_agent a where a.id between ? and ? "
|
|
||||||
+ "order by a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo);
|
|
||||||
return available("AGENT_WISE_BALANCE", List.of("Agent Code", "Name", "Current Balance"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData agentWiseCollection(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", "
|
|
||||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\" from m_dds_agent a "
|
|
||||||
+ "left join m_dds_party_transaction t on t.agent_id = a.id and t.transaction_type = 'PARTY_TRAN' "
|
|
||||||
+ "and t.direction = 'CREDIT' and t.transaction_date between ? and ? " + "where a.id between ? and ? "
|
|
||||||
+ "group by a.id, a.agent_code, a.agent_name order by a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
|
||||||
return available("AGENT_WISE_MONTHLY_COLLECTION", List.of("Agent Code", "Name", "Total Collection"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData commissionRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Name\", b.job_date as \"Job Date\", "
|
|
||||||
+ "b.posted_on_date as \"Posted On\", r.amount as \"Commission Amount\" from m_dds_batch_run_row r "
|
|
||||||
+ "join m_dds_batch_run b on b.id = r.batch_run_id join m_dds_agent a on a.id = r.agent_id "
|
|
||||||
+ "where b.job_type = 'COMMISSION_CALCULATION' and b.status = 'POSTED' and b.posted_on_date between ? and ? "
|
|
||||||
+ "and a.id between ? and ? order by b.posted_on_date, a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
|
||||||
return available("COMMISSION_REGISTER", List.of("Agent Code", "Name", "Job Date", "Posted On", "Commission Amount"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData collectionSummaryAgentWise(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
|
||||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\" from m_dds_agent a "
|
|
||||||
+ "left join m_dds_party_transaction t on t.agent_id = a.id and t.transaction_type = 'PARTY_TRAN' "
|
|
||||||
+ "and t.direction = 'CREDIT' and t.transaction_date between ? and ? " + "where a.id between ? and ? "
|
|
||||||
+ "group by a.id, a.agent_code, a.agent_name order by a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
|
||||||
return available("COLLECTION_SUMMARY_AGENT_WISE", List.of("Agent Code", "Agent Name", "Total Collection"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData agentPartywiseDailyCollection(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", p.party_code as \"Party Code\", "
|
|
||||||
+ "t.transaction_date as \"Transaction Date\", t.amount as \"Amount\", "
|
|
||||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
|
||||||
+ "join m_dds_party p on p.id = t.party_id join m_dds_agent a on a.id = t.agent_id "
|
|
||||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
|
||||||
+ "and a.id between ? and ? and p.id between ? and ? order by t.transaction_date, a.agent_code, p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("AGENT_PARTYWISE_DAILY_COLLECTION",
|
|
||||||
List.of("Agent Code", "Party Code", "Transaction Date", "Amount", "Balance After"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData dailyCollectionReport(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo) {
|
|
||||||
final String sql = "select t.transaction_date as \"Transaction Date\", a.agent_code as \"Agent Code\", "
|
|
||||||
+ "coalesce(sum(t.amount), 0) as \"Total Collection\", count(*) as \"Transaction Count\" "
|
|
||||||
+ "from m_dds_party_transaction t join m_dds_agent a on a.id = t.agent_id "
|
|
||||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
|
||||||
+ "and a.id between ? and ? group by t.transaction_date, a.id, a.agent_code "
|
|
||||||
+ "order by t.transaction_date, a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo);
|
|
||||||
return available("DAILY_COLLECTION_REPORT",
|
|
||||||
List.of("Transaction Date", "Agent Code", "Total Collection", "Transaction Count"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData partyInterestRegister(final LocalDate fromDate, final LocalDate toDate, final Long partyFrom,
|
|
||||||
final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", t.transaction_date as \"Transaction Date\", "
|
|
||||||
+ "t.transaction_type as \"Transaction Type\", t.amount as \"Amount\", "
|
|
||||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
|
||||||
+ "join m_dds_party p on p.id = t.party_id "
|
|
||||||
+ "where t.transaction_type in ('INTEREST_POST', 'OD_INTEREST_POST') and t.transaction_date between ? and ? "
|
|
||||||
+ "and p.id between ? and ? order by t.transaction_date, p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, partyFrom, partyTo);
|
|
||||||
return available("PARTY_INTEREST_REGISTER",
|
|
||||||
List.of("Party Code", "Transaction Date", "Transaction Type", "Amount", "Balance After"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData balanceSummary(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final Integer totalAgents = this.jdbcTemplate.queryForObject("select count(*) from m_dds_agent where id between ? and ?",
|
|
||||||
Integer.class, agentFrom, agentTo);
|
|
||||||
final Integer totalParties = this.jdbcTemplate.queryForObject(
|
|
||||||
"select count(*) from m_dds_party where agent_id between ? and ? and id between ? and ?", Integer.class, agentFrom,
|
|
||||||
agentTo, partyFrom, partyTo);
|
|
||||||
final BigDecimal totalBalance = this.jdbcTemplate.queryForObject(
|
|
||||||
"select coalesce(sum(current_balance), 0) from m_dds_party where agent_id between ? and ? and id between ? and ?",
|
|
||||||
BigDecimal.class, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
|
|
||||||
final Map<String, Object> row = Map.of("Total Agents", totalAgents, "Total Parties", totalParties, "Total Party Balance",
|
|
||||||
totalBalance);
|
|
||||||
return available("BALANCE_SUMMARY", List.of("Total Agents", "Total Parties", "Total Party Balance"), List.of(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData transactionRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", a.agent_code as \"Agent Code\", "
|
|
||||||
+ "t.transaction_date as \"Transaction Date\", t.transaction_type as \"Transaction Type\", "
|
|
||||||
+ "t.direction as \"Direction\", t.amount as \"Amount\", t.description as \"Description\" "
|
|
||||||
+ "from m_dds_party_transaction t join m_dds_party p on p.id = t.party_id "
|
|
||||||
+ "join m_dds_agent a on a.id = t.agent_id "
|
|
||||||
+ "where t.transaction_date between ? and ? and a.id between ? and ? and p.id between ? and ? "
|
|
||||||
+ "order by t.transaction_date, t.id";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("TRANSACTION_REGISTER",
|
|
||||||
List.of("Party Code", "Agent Code", "Transaction Date", "Transaction Type", "Direction", "Amount", "Description"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData matureDateWise(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom, final Long agentTo,
|
|
||||||
final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", p.maturity_date as \"Maturity Date\", "
|
|
||||||
+ "p.current_balance as \"Current Balance\" from m_dds_party p "
|
|
||||||
+ "where p.maturity_date is not null and p.maturity_date between ? and ? and p.agent_id between ? and ? "
|
|
||||||
+ "and p.id between ? and ? order by p.maturity_date";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("MATURE_DATE_WISE", List.of("Party Code", "Maturity Date", "Current Balance"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData interestCertificate(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
|
||||||
+ "p.opening_date as \"Opening Date\", p.daily_deposit_amount as \"Daily Deposit Amount\", "
|
|
||||||
+ "p.current_balance as \"Current Balance\", coalesce((select sum(t.amount) from m_dds_party_transaction t "
|
|
||||||
+ "where t.party_id = p.id and t.transaction_type in ('INTEREST_POST', 'OD_INTEREST_POST') "
|
|
||||||
+ "and t.transaction_date between ? and ?), 0) as \"Interest Earned\" from m_dds_party p "
|
|
||||||
+ "where p.agent_id between ? and ? and p.id between ? and ? order by p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("INTEREST_CERTIFICATE",
|
|
||||||
List.of("Party Code", "Party Name", "Opening Date", "Daily Deposit Amount", "Current Balance", "Interest Earned"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-agent settlement statement for a date range: collection volume, commission actually
|
|
||||||
* posted in that window (via the same BatchRunRow join as COMMISSION_REGISTER), and the
|
|
||||||
* deposit/advance/deduction fields tracked directly on the agent. "Deduct Commission" and
|
|
||||||
* "PC Diff" have no corresponding data anywhere in this schema yet, so they report as 0 rather
|
|
||||||
* than a guessed figure - matching the legacy sample where most of these columns were 0.00 too.
|
|
||||||
* No agent id range filter is applied here: the legacy screenshot for this report only ever
|
|
||||||
* showed a date range, no agent-range inputs.
|
|
||||||
*/
|
|
||||||
private DdsReportData agentCommissionStatement(final LocalDate fromDate, final LocalDate toDate) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
|
||||||
+ "a.agent_saving_account_no as \"A/c No\", coalesce(collection.total, 0) as \"Total Collection\", "
|
|
||||||
+ "coalesce(commission.total, 0) as \"Commission\", 0 as \"Deduct Commission\", "
|
|
||||||
+ "a.agent_deposit as \"Agent Deposit\", a.festival_advance as \"Festival Advance\", "
|
|
||||||
+ "a.other_deduction as \"Other Deduction\", "
|
|
||||||
+ "(coalesce(commission.total, 0) - a.agent_deposit - a.festival_advance - a.other_deduction) as \"Total\", "
|
|
||||||
+ "0 as \"PC Diff\" " + "from m_dds_agent a "
|
|
||||||
+ "left join (select t.agent_id as agent_id, sum(t.amount) as total from m_dds_party_transaction t "
|
|
||||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
|
||||||
+ "group by t.agent_id) collection on collection.agent_id = a.id "
|
|
||||||
+ "left join (select r.agent_id as agent_id, sum(r.amount) as total from m_dds_batch_run_row r "
|
|
||||||
+ "join m_dds_batch_run b on b.id = r.batch_run_id "
|
|
||||||
+ "where b.job_type = 'COMMISSION_CALCULATION' and b.status = 'POSTED' and b.posted_on_date between ? and ? "
|
|
||||||
+ "group by r.agent_id) commission on commission.agent_id = a.id " + "order by a.agent_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, fromDate, toDate);
|
|
||||||
return available("AGENT_COMMISSION_STATEMENT",
|
|
||||||
List.of("Agent Code", "Agent Name", "A/c No", "Total Collection", "Commission", "Deduct Commission", "Agent Deposit",
|
|
||||||
"Festival Advance", "Other Deduction", "Total", "PC Diff"),
|
|
||||||
rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy Supplementary-DDS (As On) report: every regular collection (PARTY_TRAN, CREDIT)
|
|
||||||
* posted in the given date range - clarified with the business as "the regular transactions
|
|
||||||
* going on" (2026-08-24), same shape as the other collection-listing reports.
|
|
||||||
*/
|
|
||||||
private DdsReportData supplementaryDds(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom, final Long agentTo,
|
|
||||||
final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", p.party_code as \"Party Code\", p.party_name as \"Party Name\", "
|
|
||||||
+ "t.transaction_date as \"Transaction Date\", t.transaction_mode as \"Mode\", t.amount as \"Amount\", "
|
|
||||||
+ "t.balance_after_transaction as \"Balance After\" from m_dds_party_transaction t "
|
|
||||||
+ "join m_dds_party p on p.id = t.party_id join m_dds_agent a on a.id = t.agent_id "
|
|
||||||
+ "where t.transaction_type = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transaction_date between ? and ? "
|
|
||||||
+ "and a.id between ? and ? and p.id between ? and ? order by t.transaction_date, a.agent_code, p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("SUPPLEMENTARY_DDS",
|
|
||||||
List.of("Agent Code", "Party Code", "Party Name", "Transaction Date", "Mode", "Amount", "Balance After"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy Close Party Ac Register report: Parties closed (via the Close Party command) in the
|
|
||||||
* given date range, with their close date and final (zero) balance.
|
|
||||||
*/
|
|
||||||
private DdsReportData closePartyAcRegister(final LocalDate fromDate, final LocalDate toDate, final Long agentFrom,
|
|
||||||
final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select p.party_code as \"Party Code\", p.party_name as \"Party Name\", a.agent_code as \"Agent Code\", "
|
|
||||||
+ "p.opening_date as \"Opening Date\", p.close_date as \"Close Date\", p.current_balance as \"Final Balance\" "
|
|
||||||
+ "from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
|
||||||
+ "where p.status = 'CLOSED' and p.close_date between ? and ? and a.id between ? and ? and p.id between ? and ? "
|
|
||||||
+ "order by p.close_date, p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, fromDate, toDate, agentFrom, agentTo, partyFrom,
|
|
||||||
partyTo);
|
|
||||||
return available("CLOSE_PARTY_AC_REGISTER",
|
|
||||||
List.of("Party Code", "Party Name", "Agent Code", "Opening Date", "Close Date", "Final Balance"), rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy Master Data Export report: clarified with the business (2026-08-24) as "select an
|
|
||||||
* agent, show the full detail of every party under that agent" - filtered by the same agent
|
|
||||||
* range as every other report here (a single agent is just agentFrom == agentTo).
|
|
||||||
*/
|
|
||||||
private DdsReportData masterDataExport(final Long agentFrom, final Long agentTo, final Long partyFrom, final Long partyTo) {
|
|
||||||
final String sql = "select a.agent_code as \"Agent Code\", a.agent_name as \"Agent Name\", "
|
|
||||||
+ "p.party_code as \"Party Code\", p.party_name as \"Party Name\", p.address1 as \"Address\", "
|
|
||||||
+ "p.city as \"City\", p.pincode as \"Pincode\", p.mobile_no_sms as \"Mobile No\", "
|
|
||||||
+ "p.aadhar_no as \"Aadhar No\", p.pan_card_no as \"PAN No\", p.opening_date as \"Opening Date\", "
|
|
||||||
+ "p.maturity_date as \"Maturity Date\", p.daily_deposit_amount as \"Daily Deposit Amount\", "
|
|
||||||
+ "p.interest_rate as \"Interest Rate\", p.current_balance as \"Current Balance\" "
|
|
||||||
+ "from m_dds_party p join m_dds_agent a on a.id = p.agent_id "
|
|
||||||
+ "where a.id between ? and ? and p.id between ? and ? order by a.agent_code, p.party_code";
|
|
||||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(sql, agentFrom, agentTo, partyFrom, partyTo);
|
|
||||||
return available("MASTER_DATA_EXPORT",
|
|
||||||
List.of("Agent Code", "Agent Name", "Party Code", "Party Name", "Address", "City", "Pincode", "Mobile No", "Aadhar No",
|
|
||||||
"PAN No", "Opening Date", "Maturity Date", "Daily Deposit Amount", "Interest Rate", "Current Balance"),
|
|
||||||
rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DdsReportData available(final String reportKey, final List<String> columns, final List<Map<String, Object>> rows) {
|
|
||||||
return DdsReportData.builder().reportKey(reportKey).available(true).columns(columns).rows(rows).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.report.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.report.service.DdsReportReadPlatformServiceImpl;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class DdsReportConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(DdsReportReadPlatformService.class)
|
|
||||||
public DdsReportReadPlatformService ddsReportReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new DdsReportReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-90
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.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.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.MediaType;
|
|
||||||
import java.util.Collection;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
|
||||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
|
||||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformService;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Path("/v1/dds/partytransactions")
|
|
||||||
@Component
|
|
||||||
@Tag(name = "DDS Party Transactions", description = "The Daily Transaction Entry ledger: a day's PARTY_TRAN collection rows, the two legs of a WITHDRAW_DEPOSITE transfer between two parties, or an AGENT_TRAN settlement of an Agent's collected cash to its branch. Immutable once posted - reversed with an opposing entry, never edited.")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartyTransactionsApiResource {
|
|
||||||
|
|
||||||
private static final String RESOURCE_NAME_FOR_PERMISSIONS = "PARTYTRANSACTION";
|
|
||||||
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final PartyTransactionReadPlatformService readPlatformService;
|
|
||||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve DDS Party Transactions", description = "Returns the list of posted DDS party transactions, most recent first. Optionally filter by agentId and/or partyId.")
|
|
||||||
public Collection<PartyTransactionData> retrieveAll(@QueryParam("agentId") @Parameter(description = "agentId") final Long agentId,
|
|
||||||
@QueryParam("partyId") @Parameter(description = "partyId") final Long partyId) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveAll(agentId, partyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{transactionId}")
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Retrieve a DDS Party Transaction", description = "Returns the details of a single posted DDS party transaction.")
|
|
||||||
public PartyTransactionData retrieveOne(@PathParam("transactionId") @Parameter(description = "transactionId") final Long transactionId) {
|
|
||||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
|
||||||
return readPlatformService.retrieveOne(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON })
|
|
||||||
@Operation(summary = "Post DDS Party Transactions", description = "Mandatory fields: agentId, transactionType (PARTY_TRAN, "
|
|
||||||
+ "WITHDRAW_DEPOSITE or AGENT_TRAN), transactionDate\n" + "\n"
|
|
||||||
+ "For PARTY_TRAN: collections (array of {partyCode, transactionMode, accountType, accountNo, amount, chequeNo, tokenNo, "
|
|
||||||
+ "description})\n" + "\n" + "For WITHDRAW_DEPOSITE: creditorPartyCode, debitPartyCode, withdrawAmount, withdrawDescription\n"
|
|
||||||
+ "\n" + "For AGENT_TRAN: amount, transactionMode, accountType, accountNo, description")
|
|
||||||
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
|
||||||
.createPartyTransaction() //
|
|
||||||
.withJson(apiRequestBodyAsJson) //
|
|
||||||
.build();
|
|
||||||
return commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.domain;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
|
|
||||||
public interface PartyTransactionRepository extends JpaRepository<PartyTransaction, Long> {
|
|
||||||
|
|
||||||
@Query("select coalesce(sum(t.amount), 0) from PartyTransaction t where t.agent.id = :agentId "
|
|
||||||
+ "and t.transactionType = 'PARTY_TRAN' and t.direction = 'CREDIT' and t.transactionDate between :fromDate and :toDate")
|
|
||||||
BigDecimal sumPartyTranCredits(@Param("agentId") Long agentId, @Param("fromDate") LocalDate fromDate,
|
|
||||||
@Param("toDate") LocalDate toDate);
|
|
||||||
}
|
|
||||||
-46
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.handler;
|
|
||||||
|
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@CommandType(entity = "PARTYTRANSACTION", action = "CREATE")
|
|
||||||
public class CreatePartyTransactionCommandHandler implements NewCommandSourceHandler {
|
|
||||||
|
|
||||||
private final PartyTransactionWritePlatformService writePlatformService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CreatePartyTransactionCommandHandler(final PartyTransactionWritePlatformService writePlatformService) {
|
|
||||||
this.writePlatformService = writePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
|
||||||
return this.writePlatformService.createPartyTransaction(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-137
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.serialization;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PARTY_TRAN (batch collection rows), WITHDRAW_DEPOSITE (a transfer between two parties) and
|
|
||||||
* AGENT_TRAN (an Agent settling its collected cash to a branch, no party involved) are
|
|
||||||
* validated/processed - the three transaction types the Daily Transaction Entry screen actually
|
|
||||||
* collects fields for today. PARTY_PAYMENT / OD_ENTRY / OD_PAYMENT are listed on the frontend as
|
|
||||||
* not-yet-implemented placeholders, so they are rejected here rather than silently accepted with no
|
|
||||||
* real effect.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public final class PartyTransactionCommandFromApiJsonDeserializer {
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_TRANSACTION_TYPES = new HashSet<>(
|
|
||||||
Arrays.asList("PARTY_TRAN", "WITHDRAW_DEPOSITE", "AGENT_TRAN"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(Arrays.asList("agentId", "transactionType",
|
|
||||||
"transactionDate", "collections", "creditorPartyCode", "debitPartyCode", "withdrawAmount", "withdrawDescription",
|
|
||||||
"amount", "transactionMode", "accountType", "accountNo", "description", "dateFormat", "locale"));
|
|
||||||
|
|
||||||
private static final Set<String> SUPPORTED_COLLECTION_PARAMETERS = new HashSet<>(Arrays.asList("partyCode", "transactionMode",
|
|
||||||
"accountType", "accountNo", "amount", "chequeNo", "tokenNo", "description"));
|
|
||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public PartyTransactionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateForCreate(final String json) {
|
|
||||||
if (StringUtils.isBlank(json)) {
|
|
||||||
throw new InvalidJsonException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {
|
|
||||||
|
|
||||||
}.getType();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
|
|
||||||
|
|
||||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
|
||||||
final DataValidatorBuilder v = new DataValidatorBuilder(dataValidationErrors).resource("partytransaction");
|
|
||||||
|
|
||||||
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
|
||||||
|
|
||||||
v.reset().parameter("agentId").value(this.fromApiJsonHelper.extractLongNamed("agentId", element)).notNull()
|
|
||||||
.integerGreaterThanZero();
|
|
||||||
|
|
||||||
final String transactionType = this.fromApiJsonHelper.extractStringNamed("transactionType", element);
|
|
||||||
v.reset().parameter("transactionType").value(transactionType).notBlank().isOneOfTheseStringValues(
|
|
||||||
SUPPORTED_TRANSACTION_TYPES.toArray());
|
|
||||||
|
|
||||||
v.reset().parameter("transactionDate").value(this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element)).notNull();
|
|
||||||
|
|
||||||
if ("PARTY_TRAN".equals(transactionType)) {
|
|
||||||
if (!this.fromApiJsonHelper.parameterExists("collections", element)) {
|
|
||||||
v.reset().parameter("collections").value(null).notNull();
|
|
||||||
} else {
|
|
||||||
final JsonObject topLevel = element.getAsJsonObject();
|
|
||||||
final var collectionsArray = topLevel.getAsJsonArray("collections");
|
|
||||||
v.reset().parameter("collections").value(collectionsArray).jsonArrayNotEmpty();
|
|
||||||
for (int i = 0; i < collectionsArray.size(); i++) {
|
|
||||||
final JsonObject row = collectionsArray.get(i).getAsJsonObject();
|
|
||||||
this.fromApiJsonHelper.checkForUnsupportedParameters(row, SUPPORTED_COLLECTION_PARAMETERS);
|
|
||||||
|
|
||||||
v.reset().parameterAtIndexArray("partyCode", i).value(extractString(row, "partyCode")).notBlank();
|
|
||||||
v.reset().parameterAtIndexArray("accountNo", i).value(extractString(row, "accountNo")).notBlank();
|
|
||||||
v.reset().parameterAtIndexArray("amount", i).value(extractBigDecimal(row, "amount")).notNull().positiveAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ("WITHDRAW_DEPOSITE".equals(transactionType)) {
|
|
||||||
v.reset().parameter("creditorPartyCode").value(this.fromApiJsonHelper.extractStringNamed("creditorPartyCode", element))
|
|
||||||
.notBlank();
|
|
||||||
v.reset().parameter("debitPartyCode").value(this.fromApiJsonHelper.extractStringNamed("debitPartyCode", element))
|
|
||||||
.notBlank();
|
|
||||||
v.reset().parameter("withdrawAmount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("withdrawAmount", element))
|
|
||||||
.notNull().positiveAmount();
|
|
||||||
} else if ("AGENT_TRAN".equals(transactionType)) {
|
|
||||||
v.reset().parameter("amount").value(this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("amount", element)).notNull()
|
|
||||||
.positiveAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractString(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private java.math.BigDecimal extractBigDecimal(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsBigDecimal() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
|
||||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
|
||||||
dataValidationErrors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.service;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
|
||||||
|
|
||||||
public interface PartyTransactionReadPlatformService {
|
|
||||||
|
|
||||||
Collection<PartyTransactionData> retrieveAll(Long agentId, Long partyId);
|
|
||||||
|
|
||||||
PartyTransactionData retrieveOne(Long transactionId);
|
|
||||||
}
|
|
||||||
-98
@@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.service;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
|
|
||||||
import org.apache.fineract.infrastructure.security.utils.SQLBuilder;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.data.PartyTransactionData;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.exception.PartyTransactionNotFoundException;
|
|
||||||
import org.springframework.dao.EmptyResultDataAccessException;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartyTransactionReadPlatformServiceImpl implements PartyTransactionReadPlatformService {
|
|
||||||
|
|
||||||
private final JdbcTemplate jdbcTemplate;
|
|
||||||
|
|
||||||
private static final PartyTransactionMapper MAPPER = new PartyTransactionMapper();
|
|
||||||
|
|
||||||
private static final class PartyTransactionMapper implements RowMapper<PartyTransactionData> {
|
|
||||||
|
|
||||||
public String schema() {
|
|
||||||
return " t.id as id, t.party_id as partyId, p.party_code as partyCode, p.party_name as partyName, "
|
|
||||||
+ " t.agent_id as agentId, a.agent_code as agentCode, t.transaction_date as transactionDate, "
|
|
||||||
+ " t.transaction_type as transactionType, t.direction as direction, t.transaction_mode as transactionMode, "
|
|
||||||
+ " t.account_type as accountType, t.account_no as accountNo, t.amount as amount, t.cheque_no as chequeNo, "
|
|
||||||
+ " t.token_no as tokenNo, t.description as description, t.balance_after_transaction as balanceAfterTransaction "
|
|
||||||
+ " from m_dds_party_transaction t left join m_dds_party p on p.id = t.party_id "
|
|
||||||
+ " join m_dds_agent a on a.id = t.agent_id";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartyTransactionData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
|
||||||
return PartyTransactionData.builder() //
|
|
||||||
.id(rs.getLong("id")) //
|
|
||||||
.partyId(JdbcSupport.getLongDefaultToNullIfZero(rs, "partyId")) //
|
|
||||||
.partyCode(rs.getString("partyCode")) //
|
|
||||||
.partyName(rs.getString("partyName")) //
|
|
||||||
.agentId(rs.getLong("agentId")) //
|
|
||||||
.agentCode(rs.getString("agentCode")) //
|
|
||||||
.transactionDate(JdbcSupport.getLocalDate(rs, "transactionDate")) //
|
|
||||||
.transactionType(rs.getString("transactionType")) //
|
|
||||||
.direction(rs.getString("direction")) //
|
|
||||||
.transactionMode(rs.getString("transactionMode")) //
|
|
||||||
.accountType(rs.getString("accountType")) //
|
|
||||||
.accountNo(rs.getString("accountNo")) //
|
|
||||||
.amount(rs.getBigDecimal("amount")) //
|
|
||||||
.chequeNo(rs.getString("chequeNo")) //
|
|
||||||
.tokenNo(rs.getString("tokenNo")) //
|
|
||||||
.description(rs.getString("description")) //
|
|
||||||
.balanceAfterTransaction(rs.getBigDecimal("balanceAfterTransaction")) //
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<PartyTransactionData> retrieveAll(final Long agentId, final Long partyId) {
|
|
||||||
final SQLBuilder extraCriteria = new SQLBuilder();
|
|
||||||
extraCriteria.addNonNullCriteria(" t.agent_id = ", agentId);
|
|
||||||
extraCriteria.addNonNullCriteria(" t.party_id = ", partyId);
|
|
||||||
|
|
||||||
String sql = "select " + MAPPER.schema() + " " + extraCriteria.getSQLTemplate();
|
|
||||||
sql = sql + " order by t.id desc";
|
|
||||||
|
|
||||||
return this.jdbcTemplate.query(sql, MAPPER, extraCriteria.getArguments()); // NOSONAR
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartyTransactionData retrieveOne(final Long transactionId) {
|
|
||||||
try {
|
|
||||||
final String sql = "select " + MAPPER.schema() + " where t.id = ?";
|
|
||||||
return this.jdbcTemplate.queryForObject(sql, MAPPER, transactionId); // NOSONAR
|
|
||||||
} catch (final EmptyResultDataAccessException e) {
|
|
||||||
throw new PartyTransactionNotFoundException(transactionId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.service;
|
|
||||||
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
|
|
||||||
public interface PartyTransactionWritePlatformService {
|
|
||||||
|
|
||||||
CommandProcessingResult createPartyTransaction(JsonCommand command);
|
|
||||||
}
|
|
||||||
-177
@@ -1,177 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.service;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import jakarta.persistence.PersistenceException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.organisation.agent.exception.AgentNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyAlreadyClosedException;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.exception.PartyNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransaction;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.exception.InsufficientAgentBalanceException;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.exception.InsufficientPartyBalanceException;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.serialization.PartyTransactionCommandFromApiJsonDeserializer;
|
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PartyTransactionWritePlatformServiceJpaRepositoryImpl implements PartyTransactionWritePlatformService {
|
|
||||||
|
|
||||||
private final PartyTransactionCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
|
||||||
private final PartyTransactionRepository partyTransactionRepository;
|
|
||||||
private final PartyRepository partyRepository;
|
|
||||||
private final AgentRepository agentRepository;
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
|
||||||
public CommandProcessingResult createPartyTransaction(final JsonCommand command) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.fromApiJsonDeserializer.validateForCreate(command.json());
|
|
||||||
|
|
||||||
final Long agentId = command.longValueOfParameterNamed("agentId");
|
|
||||||
final Agent agent = this.agentRepository.findById(agentId)
|
|
||||||
.orElseThrow(() -> new AgentNotFoundException(agentId));
|
|
||||||
|
|
||||||
final String transactionType = command.stringValueOfParameterNamed("transactionType");
|
|
||||||
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
|
|
||||||
|
|
||||||
final List<PartyTransaction> posted = new ArrayList<>();
|
|
||||||
if ("PARTY_TRAN".equals(transactionType)) {
|
|
||||||
posted.addAll(postCollections(agent, transactionDate, command));
|
|
||||||
} else if ("AGENT_TRAN".equals(transactionType)) {
|
|
||||||
posted.add(postAgentSettlement(agent, transactionDate, command));
|
|
||||||
} else {
|
|
||||||
posted.addAll(postWithdrawDeposite(agent, transactionDate, command));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.partyTransactionRepository.saveAllAndFlush(posted);
|
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
|
||||||
.withCommandId(command.commandId()) //
|
|
||||||
.withEntityId(posted.isEmpty() ? null : posted.get(0).getId()) //
|
|
||||||
.build();
|
|
||||||
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
|
|
||||||
handleDataIntegrityIssues(dve.getMostSpecificCause(), dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
} catch (final PersistenceException dve) {
|
|
||||||
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
|
|
||||||
handleDataIntegrityIssues(throwable, dve);
|
|
||||||
return CommandProcessingResult.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PartyTransaction> postCollections(final Agent agent, final LocalDate transactionDate, final JsonCommand command) {
|
|
||||||
final List<PartyTransaction> transactions = new ArrayList<>();
|
|
||||||
final JsonArray collectionsArray = command.arrayOfParameterNamed("collections");
|
|
||||||
for (int i = 0; i < collectionsArray.size(); i++) {
|
|
||||||
final JsonObject row = collectionsArray.get(i).getAsJsonObject();
|
|
||||||
final String partyCode = row.get("partyCode").getAsString();
|
|
||||||
final Party party = this.partyRepository.findByPartyCode(partyCode)
|
|
||||||
.orElseThrow(() -> new PartyNotFoundException(partyCode));
|
|
||||||
if (party.isClosed()) {
|
|
||||||
throw new PartyAlreadyClosedException(partyCode);
|
|
||||||
}
|
|
||||||
final BigDecimal amount = row.get("amount").getAsBigDecimal();
|
|
||||||
final BigDecimal newBalance = party.credit(amount);
|
|
||||||
agent.credit(amount);
|
|
||||||
|
|
||||||
transactions.add(new PartyTransaction(party, agent, transactionDate, "PARTY_TRAN", "CREDIT",
|
|
||||||
stringOrNull(row, "transactionMode"), stringOrNull(row, "accountType"), stringOrNull(row, "accountNo"), amount,
|
|
||||||
stringOrNull(row, "chequeNo"), stringOrNull(row, "tokenNo"), stringOrNull(row, "description"), newBalance));
|
|
||||||
}
|
|
||||||
return transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PartyTransaction postAgentSettlement(final Agent agent, final LocalDate transactionDate, final JsonCommand command) {
|
|
||||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("amount");
|
|
||||||
if (agent.getAgentCurrentBalance().compareTo(amount) < 0) {
|
|
||||||
throw new InsufficientAgentBalanceException(agent.getAgentCode());
|
|
||||||
}
|
|
||||||
final BigDecimal newBalance = agent.settle(amount);
|
|
||||||
|
|
||||||
return new PartyTransaction(null, agent, transactionDate, "AGENT_TRAN", "DEBIT",
|
|
||||||
command.stringValueOfParameterNamedAllowingNull("transactionMode"),
|
|
||||||
command.stringValueOfParameterNamedAllowingNull("accountType"),
|
|
||||||
command.stringValueOfParameterNamedAllowingNull("accountNo"), amount, null, null,
|
|
||||||
command.stringValueOfParameterNamedAllowingNull("description"), newBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PartyTransaction> postWithdrawDeposite(final Agent agent, final LocalDate transactionDate,
|
|
||||||
final JsonCommand command) {
|
|
||||||
final String creditorPartyCode = command.stringValueOfParameterNamed("creditorPartyCode");
|
|
||||||
final String debitPartyCode = command.stringValueOfParameterNamed("debitPartyCode");
|
|
||||||
final BigDecimal amount = command.bigDecimalValueOfParameterNamed("withdrawAmount");
|
|
||||||
final String description = command.stringValueOfParameterNamedAllowingNull("withdrawDescription");
|
|
||||||
|
|
||||||
final Party creditorParty = this.partyRepository.findByPartyCode(creditorPartyCode)
|
|
||||||
.orElseThrow(() -> new PartyNotFoundException(creditorPartyCode));
|
|
||||||
final Party debitParty = this.partyRepository.findByPartyCode(debitPartyCode)
|
|
||||||
.orElseThrow(() -> new PartyNotFoundException(debitPartyCode));
|
|
||||||
|
|
||||||
if (debitParty.getCurrentBalance().compareTo(amount) < 0) {
|
|
||||||
throw new InsufficientPartyBalanceException(debitPartyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
final BigDecimal debtorNewBalance = debitParty.debit(amount);
|
|
||||||
final BigDecimal creditorNewBalance = creditorParty.credit(amount);
|
|
||||||
|
|
||||||
final List<PartyTransaction> transactions = new ArrayList<>();
|
|
||||||
transactions.add(new PartyTransaction(debitParty, agent, transactionDate, "WITHDRAW_DEPOSITE", "DEBIT", null, null, null,
|
|
||||||
amount, null, null, description, debtorNewBalance));
|
|
||||||
transactions.add(new PartyTransaction(creditorParty, agent, transactionDate, "WITHDRAW_DEPOSITE", "CREDIT", null, null, null,
|
|
||||||
amount, null, null, description, creditorNewBalance));
|
|
||||||
return transactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String stringOrNull(final JsonObject json, final String paramName) {
|
|
||||||
return json.has(paramName) && !json.get(paramName).isJsonNull() ? json.get(paramName).getAsString() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDataIntegrityIssues(final Throwable realCause, final Exception dve) {
|
|
||||||
if (realCause.getMessage() != null && realCause.getMessage().contains("party_code")) {
|
|
||||||
throw new PlatformDataIntegrityException("error.msg.partytransaction.invalid.party",
|
|
||||||
"One of the party codes in this transaction does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("Error occured.", dve);
|
|
||||||
throw ErrorHandler.getMappable(dve, "error.msg.partytransaction.unknown.data.integrity.issue",
|
|
||||||
"Unknown data integrity issue with resource: " + realCause.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-51
@@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.fineract.portfolio.dds.transaction.starter;
|
|
||||||
|
|
||||||
import org.apache.fineract.organisation.agent.domain.AgentRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.party.domain.PartyRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.domain.PartyTransactionRepository;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.serialization.PartyTransactionCommandFromApiJsonDeserializer;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionReadPlatformServiceImpl;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformService;
|
|
||||||
import org.apache.fineract.portfolio.dds.transaction.service.PartyTransactionWritePlatformServiceJpaRepositoryImpl;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class DdsPartyTransactionConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(PartyTransactionReadPlatformService.class)
|
|
||||||
public PartyTransactionReadPlatformService partyTransactionReadPlatformService(JdbcTemplate jdbcTemplate) {
|
|
||||||
return new PartyTransactionReadPlatformServiceImpl(jdbcTemplate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(PartyTransactionWritePlatformService.class)
|
|
||||||
public PartyTransactionWritePlatformService partyTransactionWritePlatformService(
|
|
||||||
PartyTransactionCommandFromApiJsonDeserializer fromApiJsonDeserializer, PartyTransactionRepository partyTransactionRepository,
|
|
||||||
PartyRepository partyRepository, AgentRepository agentRepository) {
|
|
||||||
return new PartyTransactionWritePlatformServiceJpaRepositoryImpl(fromApiJsonDeserializer, partyTransactionRepository,
|
|
||||||
partyRepository, agentRepository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+62
@@ -57,14 +57,23 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
|||||||
"9752123456", "9752345678", "9752987654"
|
"9752123456", "9752345678", "9752987654"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Demo numbers that always return DISCONNECTED (found on the MNRL) without calling the external API. */
|
||||||
|
private static final Set<String> DEMO_DISCONNECTED_NUMBERS = Set.of("9000000001", "9000000002");
|
||||||
|
|
||||||
|
/** Demo numbers that always return SUSPECTED_AND_DISCONNECTED without calling the external API. */
|
||||||
|
private static final Set<String> DEMO_SUSPECTED_AND_DISCONNECTED_NUMBERS = Set.of("9000000003");
|
||||||
|
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
|
private final boolean enabled;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final MobileLookupLogRepository logRepository;
|
private final MobileLookupLogRepository logRepository;
|
||||||
private final JdbcTemplate jdbcTemplate;
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
public MobileLookupServiceImpl(@Value("${fineract.mobile-lookup.base-url}") String baseUrl,
|
public MobileLookupServiceImpl(@Value("${fineract.mobile-lookup.base-url}") String baseUrl,
|
||||||
|
@Value("${fineract.mobile-lookup.enabled:true}") boolean enabled,
|
||||||
MobileLookupLogRepository logRepository, JdbcTemplate jdbcTemplate) {
|
MobileLookupLogRepository logRepository, JdbcTemplate jdbcTemplate) {
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
|
this.enabled = enabled;
|
||||||
this.restTemplate = new RestTemplate();
|
this.restTemplate = new RestTemplate();
|
||||||
this.logRepository = logRepository;
|
this.logRepository = logRepository;
|
||||||
this.jdbcTemplate = jdbcTemplate;
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
@@ -103,6 +112,39 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
|||||||
return MobileLookupResult.clean(logId, trimmed);
|
return MobileLookupResult.clean(logId, trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Demo numbers — return DISCONNECTED (on the MNRL) without calling the external API
|
||||||
|
if (isDemoDisconnectedNumber(trimmed)) {
|
||||||
|
LOG.info("Demo disconnected number detected: {}", trimmed);
|
||||||
|
final MobileLookupDetailData disconnectedDetail = MobileLookupDetailData.forDisconnected(
|
||||||
|
"Number revoked and listed on MNRL (demo data)", "DEMO");
|
||||||
|
final Long logId = saveLog(trimmed, "DISCONNECTED", false, true,
|
||||||
|
"Number is DISCONNECTED — found in MNRL records", null, source, clientId);
|
||||||
|
return MobileLookupResult.disconnected(logId, trimmed, disconnectedDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo numbers — return SUSPECTED_AND_DISCONNECTED without calling the external API
|
||||||
|
if (isDemoSuspectedAndDisconnectedNumber(trimmed)) {
|
||||||
|
LOG.info("Demo suspected+disconnected number detected: {}", trimmed);
|
||||||
|
final MobileLookupDetailData suspectedDetail = MobileLookupDetailData.forSuspected(
|
||||||
|
"active", "Demo DIP suspected number", "DEMO");
|
||||||
|
final MobileLookupDetailData disconnectedDetail = MobileLookupDetailData.forDisconnected(
|
||||||
|
"Number revoked and listed on MNRL (demo data)", "DEMO");
|
||||||
|
final Long logId = saveLog(trimmed, "SUSPECTED_AND_DISCONNECTED", true, true,
|
||||||
|
"Number is SUSPECTED and DISCONNECTED — found in both DIP and MNRL records", null, source, clientId);
|
||||||
|
return MobileLookupResult.suspectedAndDisconnected(logId, trimmed, suspectedDetail, disconnectedDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup switched off by config (fineract.mobile-lookup.enabled=false) -- skip the external
|
||||||
|
// call entirely rather than fail with a connection error. Logged distinctly from a genuine
|
||||||
|
// CLEAN result so it can never be mistaken for one in an audit trail.
|
||||||
|
if (!enabled) {
|
||||||
|
LOG.info("DIP/MNRL lookup is disabled by configuration — skipping check for {}", trimmed);
|
||||||
|
final Long logId = saveLog(trimmed, MobileLookupLog.STATUS_CLEAN, false, false,
|
||||||
|
"DIP/MNRL lookup is disabled by configuration — the number was not actually checked.",
|
||||||
|
"{\"provider\":\"LOOKUP_DISABLED_BY_CONFIG\"}", source, clientId);
|
||||||
|
return MobileLookupResult.clean(logId, trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
final String url = baseUrl + "/api/v1/lookup/check?mobileNumber=" + trimmed;
|
final String url = baseUrl + "/api/v1/lookup/check?mobileNumber=" + trimmed;
|
||||||
|
|
||||||
LOG.info("Calling DIP/MNRL lookup API: {}", url);
|
LOG.info("Calling DIP/MNRL lookup API: {}", url);
|
||||||
@@ -402,6 +444,26 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isDemoDisconnectedNumber(final String mobileNumber) {
|
||||||
|
if (DEMO_DISCONNECTED_NUMBERS.contains(mobileNumber)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mobileNumber.startsWith("91") && mobileNumber.length() > 10) {
|
||||||
|
return DEMO_DISCONNECTED_NUMBERS.contains(mobileNumber.substring(2));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDemoSuspectedAndDisconnectedNumber(final String mobileNumber) {
|
||||||
|
if (DEMO_SUSPECTED_AND_DISCONNECTED_NUMBERS.contains(mobileNumber)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (mobileNumber.startsWith("91") && mobileNumber.length() > 10) {
|
||||||
|
return DEMO_SUSPECTED_AND_DISCONNECTED_NUMBERS.contains(mobileNumber.substring(2));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private String getStringOrNull(JsonObject json, String key) {
|
private String getStringOrNull(JsonObject json, String key) {
|
||||||
JsonElement el = json.get(key);
|
JsonElement el = json.get(key);
|
||||||
return (el != null && !el.isJsonNull()) ? el.getAsString() : null;
|
return (el != null && !el.isJsonNull()) ? el.getAsString() : null;
|
||||||
|
|||||||
+69
@@ -18,6 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.savings.api;
|
package org.apache.fineract.portfolio.savings.api;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.documentmanagement.api.DocumentApiConstants.DOCUMENT_API_PARAM_DESCRIPTION;
|
||||||
|
import static org.apache.fineract.infrastructure.documentmanagement.api.DocumentApiConstants.DOCUMENT_API_PARAM_FILE;
|
||||||
|
import static org.apache.fineract.infrastructure.documentmanagement.api.DocumentApiConstants.DOCUMENT_API_PARAM_NAME;
|
||||||
|
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
@@ -26,6 +31,7 @@ import jakarta.ws.rs.Consumes;
|
|||||||
import jakarta.ws.rs.DELETE;
|
import jakarta.ws.rs.DELETE;
|
||||||
import jakarta.ws.rs.DefaultValue;
|
import jakarta.ws.rs.DefaultValue;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.HeaderParam;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
import jakarta.ws.rs.PUT;
|
import jakarta.ws.rs.PUT;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
@@ -34,9 +40,11 @@ import jakarta.ws.rs.Produces;
|
|||||||
import jakarta.ws.rs.QueryParam;
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.Context;
|
import jakarta.ws.rs.core.Context;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -50,6 +58,10 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamE
|
|||||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.api.DocumentApiResource;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentCreateResponse;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
|
||||||
|
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentDeleteResponse;
|
||||||
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||||
@@ -63,6 +75,9 @@ import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformS
|
|||||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountEditWritePlatformService;
|
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountEditWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
|
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService;
|
import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
||||||
|
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Path("/v1/currentaccounts")
|
@Path("/v1/currentaccounts")
|
||||||
@@ -84,6 +99,10 @@ public class CurrentAccountsApiResource {
|
|||||||
private final CurrentAccountEditWritePlatformService currentAccountEditWritePlatformService;
|
private final CurrentAccountEditWritePlatformService currentAccountEditWritePlatformService;
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final DefaultToApiJsonSerializer<CurrentAccountPendingEditData> pendingEditApiJsonSerializer;
|
private final DefaultToApiJsonSerializer<CurrentAccountPendingEditData> pendingEditApiJsonSerializer;
|
||||||
|
private final DocumentApiResource documentApiResource;
|
||||||
|
|
||||||
|
/** Matches the {@code entityType} current-account document requests are stored/looked up under. */
|
||||||
|
private static final String DOCUMENT_ENTITY_TYPE = "currentaccounts";
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("template")
|
@Path("template")
|
||||||
@@ -337,4 +356,54 @@ public class CurrentAccountsApiResource {
|
|||||||
return this.toApiJsonSerializer.serialize(result);
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Documents ────────────────────────────────────────────────────────────
|
||||||
|
// The generic /v1/{entityType}/{entityId}/documents resource (DocumentApiResource) never gets a
|
||||||
|
// chance to handle current-account requests: this class's own literal /v1/currentaccounts path
|
||||||
|
// is matched first for anything under {accountId}, and it has no "documents" method, so the
|
||||||
|
// request dead-ends in a 404 instead of falling through. These delegate straight to the same
|
||||||
|
// DocumentApiResource so current accounts get identical document-management behaviour.
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{accountId}/documents")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "List documents attached to a current account")
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK")
|
||||||
|
public List<DocumentData> retrieveDocuments(@PathParam("accountId") final Long accountId) {
|
||||||
|
return documentApiResource.retrieveAllDocuments(DOCUMENT_ENTITY_TYPE, accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{accountId}/documents/{documentId}/attachment")
|
||||||
|
@Produces({ MediaType.APPLICATION_OCTET_STREAM })
|
||||||
|
@Operation(summary = "Download a document attached to a current account")
|
||||||
|
@ApiResponse(responseCode = "200", description = "Not Shown: The corresponding Binary file")
|
||||||
|
public Response downloadDocument(@PathParam("accountId") final Long accountId, @PathParam("documentId") final Long documentId) {
|
||||||
|
return documentApiResource.downloadFile(DOCUMENT_ENTITY_TYPE, accountId, documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{accountId}/documents")
|
||||||
|
@Consumes({ MediaType.MULTIPART_FORM_DATA })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Attach a document to a current account")
|
||||||
|
@ApiResponse(responseCode = "200", description = "Not Shown (multi-part form data)")
|
||||||
|
public DocumentCreateResponse createDocument(@PathParam("accountId") final Long accountId,
|
||||||
|
@HeaderParam(CONTENT_LENGTH) final Long fileSize, @FormDataParam(DOCUMENT_API_PARAM_FILE) final InputStream inputStream,
|
||||||
|
@FormDataParam(DOCUMENT_API_PARAM_FILE) final FormDataContentDisposition fileDetails,
|
||||||
|
@FormDataParam(DOCUMENT_API_PARAM_FILE) final FormDataBodyPart filePart,
|
||||||
|
@FormDataParam(DOCUMENT_API_PARAM_NAME) final String name,
|
||||||
|
@FormDataParam(DOCUMENT_API_PARAM_DESCRIPTION) final String description) {
|
||||||
|
return documentApiResource.createDocument(DOCUMENT_ENTITY_TYPE, accountId, fileSize, inputStream, fileDetails, filePart, name,
|
||||||
|
description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("{accountId}/documents/{documentId}")
|
||||||
|
@Operation(summary = "Remove a document attached to a current account")
|
||||||
|
@ApiResponse(responseCode = "200", description = "OK")
|
||||||
|
public DocumentDeleteResponse deleteDocument(@PathParam("accountId") final Long accountId,
|
||||||
|
@PathParam("documentId") final Long documentId) {
|
||||||
|
return documentApiResource.deleteDocument(DOCUMENT_ENTITY_TYPE, accountId, documentId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -440,6 +440,9 @@ public class FixedDepositAccountsApiResource {
|
|||||||
} else if (is(commandParam, "prematureClose")) {
|
} else if (is(commandParam, "prematureClose")) {
|
||||||
final CommandWrapper commandRequest = builder.prematureCloseFixedDepositAccount(accountId).build();
|
final CommandWrapper commandRequest = builder.prematureCloseFixedDepositAccount(accountId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
} else if (is(commandParam, "payment")) {
|
||||||
|
final CommandWrapper commandRequest = builder.paymentFixedDepositAccount(accountId).build();
|
||||||
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
} else if (is(commandParam, "calculatePrematureAmount")) {
|
} else if (is(commandParam, "calculatePrematureAmount")) {
|
||||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
||||||
@@ -453,7 +456,7 @@ public class FixedDepositAccountsApiResource {
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam,
|
throw new UnrecognizedQueryParamException("command", commandParam,
|
||||||
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest",
|
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest",
|
||||||
"postInterest", "close", "prematureClose", "calculatePrematureAmount" });
|
"postInterest", "close", "prematureClose", "payment", "calculatePrematureAmount" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
@@ -489,6 +492,8 @@ public class FixedDepositAccountsApiResource {
|
|||||||
DepositAccountData account = null;
|
DepositAccountData account = null;
|
||||||
if (is(commandParam, "close")) {
|
if (is(commandParam, "close")) {
|
||||||
account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.FIXED_DEPOSIT, accountId);
|
account = this.depositAccountReadPlatformService.retrieveOneWithClosureTemplate(DepositAccountType.FIXED_DEPOSIT, accountId);
|
||||||
|
} else if (is(commandParam, "payment")) {
|
||||||
|
account = this.depositAccountReadPlatformService.retrieveOneWithPaymentTemplate(DepositAccountType.FIXED_DEPOSIT, accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||||
|
|||||||
+65
-1
@@ -97,6 +97,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||||
|
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -105,11 +106,14 @@ public class DepositAccountDataValidator {
|
|||||||
|
|
||||||
private final FromJsonHelper fromApiJsonHelper;
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
private final DepositProductDataValidator productDataValidator;
|
private final DepositProductDataValidator productDataValidator;
|
||||||
|
private final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DepositAccountDataValidator(final FromJsonHelper fromApiJsonHelper, final DepositProductDataValidator productDataValidator) {
|
public DepositAccountDataValidator(final FromJsonHelper fromApiJsonHelper, final DepositProductDataValidator productDataValidator,
|
||||||
|
final SavingsAccountRepositoryWrapper savingsAccountRepositoryWrapper) {
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
this.productDataValidator = productDataValidator;
|
this.productDataValidator = productDataValidator;
|
||||||
|
this.savingsAccountRepositoryWrapper = savingsAccountRepositoryWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateFixedDepositForSubmit(final String json) {
|
public void validateFixedDepositForSubmit(final String json) {
|
||||||
@@ -351,10 +355,40 @@ public class DepositAccountDataValidator {
|
|||||||
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
||||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||||
|
|
||||||
|
// Withdraw Deposit books the maturity proceeds straight out of the bank's cash/vault GL the
|
||||||
|
// moment the account matures -- before the customer has actually come in to collect it. For
|
||||||
|
// maturities processed automatically by the scheduled job (not a live counter transaction),
|
||||||
|
// that books cash as disbursed while it is still physically in the vault, so this option is
|
||||||
|
// disabled: every account must resolve to Transfer to Savings or a Reinvest option instead.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.withdraw.deposit.not.allowed",
|
||||||
|
"Withdraw Deposit is not a valid maturity instruction -- choose Transfer to Savings or a Reinvest option.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial Reinvest needs a specific kept-amount decided at the moment of closure -- there is
|
||||||
|
// nothing sensible to pre-commit to years in advance as a standing instruction, so it is only
|
||||||
|
// ever chosen manually via the Payment action, never set here.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.partial.reinvest.not.allowed",
|
||||||
|
"Partial Re-Invest is not a valid standing maturity instruction -- it can only be chosen manually at closure time.");
|
||||||
|
}
|
||||||
|
|
||||||
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
||||||
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
||||||
.longGreaterThanZero();
|
.longGreaterThanZero();
|
||||||
|
|
||||||
|
// Transfer to Savings can only ever post into a genuine Savings account -- Current accounts
|
||||||
|
// live in a separate ledger/module that this maturity-closure transfer cannot write into.
|
||||||
|
if (transferToSavingsId != null && transferToSavingsId > 0
|
||||||
|
&& !this.savingsAccountRepositoryWrapper.existsWithDepositAccountType(transferToSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT)) {
|
||||||
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId)
|
||||||
|
.failWithCodeAndMessage("transfer.to.savings.id.is.not.a.savings.account",
|
||||||
|
"The selected account is not a Savings account -- Transfer to Savings can only target a Savings account.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,10 +527,40 @@ public class DepositAccountDataValidator {
|
|||||||
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).notNull()
|
||||||
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||||
|
|
||||||
|
// Withdraw Deposit books the maturity proceeds straight out of the bank's cash/vault GL the
|
||||||
|
// moment the account matures -- before the customer has actually come in to collect it. For
|
||||||
|
// maturities processed automatically by the scheduled job (not a live counter transaction),
|
||||||
|
// that books cash as disbursed while it is still physically in the vault, so this option is
|
||||||
|
// disabled: every account must resolve to Transfer to Savings or a Reinvest option instead.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.WITHDRAW_DEPOSIT.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.withdraw.deposit.not.allowed",
|
||||||
|
"Withdraw Deposit is not a valid maturity instruction -- choose Transfer to Savings or a Reinvest option.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial Reinvest needs a specific kept-amount decided at the moment of closure -- there is
|
||||||
|
// nothing sensible to pre-commit to years in advance as a standing instruction, so it is only
|
||||||
|
// ever chosen manually via the Payment action, never set here.
|
||||||
|
if (depositRolloverId.equals(DepositAccountOnClosureType.PARTIAL_REINVEST.getValue())) {
|
||||||
|
baseDataValidator.reset().parameter(maturityInstructionIdParamName).value(depositRolloverId).failWithCodeAndMessage(
|
||||||
|
"maturity.instruction.partial.reinvest.not.allowed",
|
||||||
|
"Partial Re-Invest is not a valid standing maturity instruction -- it can only be chosen manually at closure time.");
|
||||||
|
}
|
||||||
|
|
||||||
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
if (depositRolloverId.equals(DepositAccountOnClosureType.TRANSFER_TO_SAVINGS.getValue())) {
|
||||||
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
final Long transferToSavingsId = this.fromApiJsonHelper.extractLongNamed(transferToSavingsIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId).notNull()
|
||||||
.longGreaterThanZero();
|
.longGreaterThanZero();
|
||||||
|
|
||||||
|
// Transfer to Savings can only ever post into a genuine Savings account -- Current accounts
|
||||||
|
// live in a separate ledger/module that this maturity-closure transfer cannot write into.
|
||||||
|
if (transferToSavingsId != null && transferToSavingsId > 0
|
||||||
|
&& !this.savingsAccountRepositoryWrapper.existsWithDepositAccountType(transferToSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT)) {
|
||||||
|
baseDataValidator.reset().parameter(transferToSavingsIdParamName).value(transferToSavingsId)
|
||||||
|
.failWithCodeAndMessage("transfer.to.savings.id.is.not.a.savings.account",
|
||||||
|
"The selected account is not a Savings account -- Transfer to Savings can only target a Savings account.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+83
-1
@@ -82,6 +82,13 @@ public class DepositAccountTransactionDataValidator {
|
|||||||
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
private static final Set<String> DEPOSIT_ACCOUNT_PRE_MATURE_CALCULATION_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||||
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName));
|
||||||
|
|
||||||
|
private static final Set<String> DEPOSIT_ACCOUNT_PAYMENT_REQUEST_DATA_PARAMETERS = new HashSet<>(
|
||||||
|
Arrays.asList(DepositsApiConstants.localeParamName, DepositsApiConstants.dateFormatParamName, closedOnDateParamName,
|
||||||
|
DepositsApiConstants.noteParamName, onAccountClosureIdParamName, paymentTypeIdParamName,
|
||||||
|
transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName,
|
||||||
|
bankNumberParamName, DepositsApiConstants.transferDescriptionParamName, toSavingsAccountIdParamName,
|
||||||
|
DepositsApiConstants.reinvestAmountParamName));
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
public DepositAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
@@ -222,7 +229,15 @@ public class DepositAccountTransactionDataValidator {
|
|||||||
|
|
||||||
if (onAccountClosureId != null) {
|
if (onAccountClosureId != null) {
|
||||||
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||||
if (accountOnClosureType.isTransferToSavings()) {
|
if (accountOnClosureType.isPartialReinvest()) {
|
||||||
|
// Partial Re-Invest needs a kept-amount and a destination account that only the Payment
|
||||||
|
// command collects, and only its handler implements. Neither the FD nor the RD closure
|
||||||
|
// handler has a branch for it, so without this it would fall through to their terminal
|
||||||
|
// "withdraw the whole balance" case and silently pay the deposit out in cash.
|
||||||
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).failWithCode(
|
||||||
|
"partial.reinvest.not.allowed",
|
||||||
|
"Partial Re-Invest is only supported through the Payment action, not account closure");
|
||||||
|
} else if (accountOnClosureType.isTransferToSavings()) {
|
||||||
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||||
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||||
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName,
|
||||||
@@ -247,6 +262,73 @@ public class DepositAccountTransactionDataValidator {
|
|||||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the FD "Payment" command -- kept independent of {@link #validateClosing}, which stays exactly as-is
|
||||||
|
* for Close/Premature Close, so this never changes either of their existing behavior. Unlike
|
||||||
|
* {@code validateClosing}, reinvest is always allowed here (there is no {@code isPreMatureClose} restriction --
|
||||||
|
* Payment is meant to work on an Active account too), and a new Partial Reinvest closure type needs its own
|
||||||
|
* amount and destination-account rules.
|
||||||
|
*/
|
||||||
|
public void validatePayment(final JsonCommand command) {
|
||||||
|
final String json = command.json();
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
throw new InvalidJsonException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, DEPOSIT_ACCOUNT_PAYMENT_REQUEST_DATA_PARAMETERS);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||||
|
.resource(DepositAccountType.FIXED_DEPOSIT.resourceName());
|
||||||
|
|
||||||
|
final JsonElement element = command.parsedJson();
|
||||||
|
|
||||||
|
final LocalDate closedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(closedOnDateParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(closedOnDateParamName).value(closedOnDate).notNull();
|
||||||
|
|
||||||
|
final Integer onAccountClosureId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(onAccountClosureIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).notBlank()
|
||||||
|
.isOneOfTheseValues(DepositAccountOnClosureType.integerValues());
|
||||||
|
|
||||||
|
if (onAccountClosureId != null) {
|
||||||
|
final DepositAccountOnClosureType accountOnClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||||
|
if (accountOnClosureType.isWithdarwDeposit()) {
|
||||||
|
baseDataValidator.reset().parameter(onAccountClosureIdParamName).value(onAccountClosureId).failWithCodeAndMessage(
|
||||||
|
"withdraw.deposit.not.allowed.for.payment",
|
||||||
|
"Withdraw Deposit is not supported via Payment -- choose Transfer to Savings or a Reinvest option.");
|
||||||
|
}
|
||||||
|
// Interest earned under Reinvest Principal Only has to leave the deposit somewhere -- the same
|
||||||
|
// destination-account rule Transfer to Savings and Partial Reinvest already need.
|
||||||
|
if (accountOnClosureType.isTransferToSavings() || accountOnClosureType.isPartialReinvest()
|
||||||
|
|| accountOnClosureType.isReinvestPrincipal()) {
|
||||||
|
final Long toSavingsAccountId = this.fromApiJsonHelper.extractLongNamed(toSavingsAccountIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(toSavingsAccountIdParamName).value(toSavingsAccountId)
|
||||||
|
.cantBeBlankWhenParameterProvidedIs(onAccountClosureIdParamName, accountOnClosureType.getCode());
|
||||||
|
}
|
||||||
|
if (accountOnClosureType.isPartialReinvest()) {
|
||||||
|
final BigDecimal reinvestAmount = this.fromApiJsonHelper
|
||||||
|
.extractBigDecimalWithLocaleNamed(DepositsApiConstants.reinvestAmountParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(DepositsApiConstants.reinvestAmountParamName).value(reinvestAmount).notNull()
|
||||||
|
.positiveAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all string payment detail fields for max length
|
||||||
|
final Integer paymentTypeId = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(paymentTypeIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(paymentTypeIdParamName).value(paymentTypeId).ignoreIfNull().integerGreaterThanZero();
|
||||||
|
final Set<String> paymentDetailParameters = new HashSet<>(Arrays.asList(transactionAccountNumberParamName, checkNumberParamName,
|
||||||
|
routingCodeParamName, receiptNumberParamName, bankNumberParamName));
|
||||||
|
for (final String paymentDetailParameterName : paymentDetailParameters) {
|
||||||
|
final String paymentDetailParameterValue = this.fromApiJsonHelper.extractStringNamed(paymentDetailParameterName, element);
|
||||||
|
baseDataValidator.reset().parameter(paymentDetailParameterName).value(paymentDetailParameterValue).ignoreIfNull()
|
||||||
|
.notExceedingLengthOf(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
//
|
//
|
||||||
|
|||||||
+29
-1
@@ -87,6 +87,7 @@ import org.apache.fineract.portfolio.group.exception.CenterNotActiveException;
|
|||||||
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
|
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
|
||||||
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
|
||||||
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
|
||||||
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetailAssembler;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||||
@@ -97,9 +98,11 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
|
|||||||
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
|
||||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
|
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionDTO;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||||
import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.FixedDepositProductNotFoundException;
|
||||||
import org.apache.fineract.portfolio.savings.exception.RecurringDepositProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.RecurringDepositProductNotFoundException;
|
||||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -123,6 +126,7 @@ public class DepositAccountAssembler {
|
|||||||
|
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||||
|
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
|
public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
|
||||||
@@ -134,7 +138,8 @@ public class DepositAccountAssembler {
|
|||||||
final RecurringDepositProductRepository recurringDepositProductRepository,
|
final RecurringDepositProductRepository recurringDepositProductRepository,
|
||||||
final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context,
|
final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context,
|
||||||
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory,
|
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory,
|
||||||
final SavingsAccountAssembler savingsAccountAssembler) {
|
final SavingsAccountAssembler savingsAccountAssembler,
|
||||||
|
final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService) {
|
||||||
|
|
||||||
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
|
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
|
||||||
this.clientRepository = clientRepository;
|
this.clientRepository = clientRepository;
|
||||||
@@ -151,6 +156,7 @@ public class DepositAccountAssembler {
|
|||||||
this.paymentDetailAssembler = paymentDetailAssembler;
|
this.paymentDetailAssembler = paymentDetailAssembler;
|
||||||
this.externalIdFactory = externalIdFactory;
|
this.externalIdFactory = externalIdFactory;
|
||||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||||
|
this.fdBaseInterestRateReadPlatformService = fdBaseInterestRateReadPlatformService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -322,6 +328,28 @@ public class DepositAccountAssembler {
|
|||||||
accountChart = DepositAccountInterestRateChart.from(productChart);
|
accountChart = DepositAccountInterestRateChart.from(productChart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A new FD priced from its product's own chart never carries a senior-citizen premium -- that
|
||||||
|
// concept exists only on the CBS Base Interest Rate master. So whenever staff hasn't picked a
|
||||||
|
// specific chart, price the account from that master's currently active version instead (the same
|
||||||
|
// one the Base Interest Rates screen shows and renewals already use), so a senior-citizen FD shows
|
||||||
|
// its premium from day one rather than only after its first renewal. RD is unaffected: this only
|
||||||
|
// runs when depositAccountType.isFixedDeposit(), and it's a no-op wherever no active base-rate
|
||||||
|
// version exists for the product's FD type, in which case the product's own chart above stands.
|
||||||
|
if (depositAccountType.isFixedDeposit() && accountChart != null && !command.parameterExists(chartIdParamName)
|
||||||
|
&& product instanceof FixedDepositProduct) {
|
||||||
|
final String fdType = ((FixedDepositProduct) product).getFdType();
|
||||||
|
if (fdType != null) {
|
||||||
|
final FdInterestRateEffectiveData activeBaseRates = this.fdBaseInterestRateReadPlatformService
|
||||||
|
.retrieveActiveInterestRatesByType(fdType).stream().findFirst().orElse(null);
|
||||||
|
if (activeBaseRates != null && activeBaseRates.getSlabs() != null && !activeBaseRates.getSlabs().isEmpty()) {
|
||||||
|
final InterestRateChartFields baseChartFields = DepositAccountDomainServiceJpa.baseChartFieldsOf(activeBaseRates);
|
||||||
|
final Collection<DepositAccountInterestRateChart.SlabSnapshot> baseChartSlabs = DepositAccountDomainServiceJpa
|
||||||
|
.baseChartSlabsOf(activeBaseRates, product.currency().getCode());
|
||||||
|
accountChart.refreshFromSlabSnapshots(baseChartFields, baseChartSlabs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean withHoldTax = product.withHoldTax();
|
boolean withHoldTax = product.withHoldTax();
|
||||||
if (command.parameterExists(withHoldTaxParamName)) {
|
if (command.parameterExists(withHoldTaxParamName)) {
|
||||||
withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
|
withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
|
||||||
|
|||||||
+11
@@ -35,6 +35,14 @@ public interface DepositAccountDomainService {
|
|||||||
SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||||
BigDecimal transactionAmount, PaymentDetail paymentDetail);
|
BigDecimal transactionAmount, PaymentDetail paymentDetail);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As above, but lets the caller declare the deposit as an internal account-to-account movement rather
|
||||||
|
* than cash taken over the counter. Used by reinvestment on maturity, where the funds come straight
|
||||||
|
* from the matured deposit and must post against the transfer-suspense GL instead of cash/vault.
|
||||||
|
*/
|
||||||
|
SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||||
|
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer);
|
||||||
|
|
||||||
SavingsAccountTransaction handleRDDeposit(RecurringDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
SavingsAccountTransaction handleRDDeposit(RecurringDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
|
||||||
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isRegularTransaction);
|
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isRegularTransaction);
|
||||||
|
|
||||||
@@ -56,4 +64,7 @@ public interface DepositAccountDomainService {
|
|||||||
|
|
||||||
Long handleRDAccountPreMatureClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
|
Long handleRDAccountPreMatureClosure(RecurringDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
|
||||||
Map<String, Object> changes);
|
Map<String, Object> changes);
|
||||||
|
|
||||||
|
Long handleFDAccountPayment(FixedDepositAccount account, PaymentDetail paymentDetail, AppUser user, JsonCommand command,
|
||||||
|
Map<String, Object> changes);
|
||||||
}
|
}
|
||||||
|
|||||||
+629
-24
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.savings.domain;
|
package org.apache.fineract.portfolio.savings.domain;
|
||||||
|
|
||||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.onAccountClosureIdParamName;
|
||||||
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.reinvestAmountParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.toSavingsAccountIdParamName;
|
||||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferDescriptionParamName;
|
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferDescriptionParamName;
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ import java.math.BigDecimal;
|
|||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
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.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -55,12 +59,18 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
|
|||||||
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
|
import org.apache.fineract.portfolio.calendar.domain.CalendarType;
|
||||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||||
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields;
|
||||||
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartSlabFields;
|
||||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||||
|
import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData;
|
||||||
|
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
import org.apache.fineract.portfolio.savings.service.SavingsAccountDomainService;
|
||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -80,13 +90,28 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
|
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
|
||||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
|
private final FixedDepositRenewalHistoryRepository fixedDepositRenewalHistoryRepository;
|
||||||
|
private final FixedDepositRenewalChartSlabHistoryRepository fixedDepositRenewalChartSlabHistoryRepository;
|
||||||
|
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
|
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||||
final boolean applyWithdrawFee, final boolean isRegularTransaction) {
|
final boolean applyWithdrawFee, final boolean isRegularTransaction) {
|
||||||
boolean isAccountTransfer = false;
|
final boolean isAccountTransfer = false;
|
||||||
|
return handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail, applyWithdrawFee, isRegularTransaction,
|
||||||
|
isAccountTransfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As above, but lets the caller declare the withdrawal as an internal account-to-account movement
|
||||||
|
* rather than cash paid out over the counter. Used by reinvestment on maturity, where the balance
|
||||||
|
* moves straight into the new deposit and must post against the transfer-suspense GL, not cash/vault.
|
||||||
|
*/
|
||||||
|
private SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
|
||||||
|
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||||
|
final boolean applyWithdrawFee, final boolean isRegularTransaction, final boolean isAccountTransfer) {
|
||||||
boolean isInterestTransfer = false;
|
boolean isInterestTransfer = false;
|
||||||
boolean isWithdrawBalance = false;
|
boolean isWithdrawBalance = false;
|
||||||
final boolean backdatedTxnsAllowedTill = false;
|
final boolean backdatedTxnsAllowedTill = false;
|
||||||
@@ -100,7 +125,15 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
@Override
|
@Override
|
||||||
public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt,
|
public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt,
|
||||||
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
|
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
|
||||||
boolean isAccountTransfer = false;
|
final boolean isAccountTransfer = false;
|
||||||
|
return handleFDDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt,
|
||||||
|
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
|
||||||
|
final boolean isAccountTransfer) {
|
||||||
boolean isRegularTransaction = false;
|
boolean isRegularTransaction = false;
|
||||||
final boolean backdatedTxnsAllowedTill = false;
|
final boolean backdatedTxnsAllowedTill = false;
|
||||||
return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail,
|
return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail,
|
||||||
@@ -200,16 +233,26 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
|
final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
|
||||||
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||||
if (onClosureType.isReinvest()) {
|
if (onClosureType.isReinvest()) {
|
||||||
|
// Captured before the withdrawal below empties the account, so the renewal history can
|
||||||
|
// record what this term actually matured at.
|
||||||
|
final BigDecimal maturedBalance = account.getAccountBalance();
|
||||||
ExternalId externalId = this.externalIdFactory.create();
|
ExternalId externalId = this.externalIdFactory.create();
|
||||||
FixedDepositAccount reinvestedDeposit = account.reInvest(account.getAccountBalance(), externalId);
|
FixedDepositAccount reinvestedDeposit = account.reInvest(maturedBalance, externalId);
|
||||||
this.depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit);
|
this.depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit);
|
||||||
reinvestedDeposit.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
|
reinvestedDeposit.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
|
||||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
this.savingsAccountRepository.saveAndFlush(reinvestedDeposit);
|
this.savingsAccountRepository.saveAndFlush(reinvestedDeposit);
|
||||||
autoGenerateAccountNumber(reinvestedDeposit);
|
autoGenerateAccountNumber(reinvestedDeposit);
|
||||||
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
// Renewal is an internal movement into the new deposit, not cash paid out -- post it
|
||||||
paymentDetail, false, isRegularTransaction);
|
// against the transfer-suspense GL rather than crediting cash/vault.
|
||||||
|
final boolean isInternalTransferLeg = true;
|
||||||
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, maturedBalance, paymentDetail,
|
||||||
|
false, isRegularTransaction, isInternalTransferLeg);
|
||||||
savingsTransactionId = withdrawal.getId();
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
// Reached when a user closes the account with a re-invest instruction, rather than the
|
||||||
|
// maturity job acting on a standing instruction.
|
||||||
|
recordRenewal(account, reinvestedDeposit, maturedBalance, maturedBalance, closedDate,
|
||||||
|
FixedDepositRenewalHistory.RENEWAL_TYPE_MANUAL, user);
|
||||||
} else if (onClosureType.isTransferToSavings()) {
|
} else if (onClosureType.isTransferToSavings()) {
|
||||||
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
|
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
|
||||||
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
|
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
|
||||||
@@ -260,13 +303,58 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
account.setClosedOnDate(closedDate);
|
account.setClosedOnDate(closedDate);
|
||||||
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||||
if (onClosureType.isReinvest()) {
|
if (onClosureType.isReinvestPrincipalAndInterest()) {
|
||||||
BigDecimal reInvestAmount;
|
// Renews on the SAME account, so the customer keeps one account number however many times
|
||||||
if (onClosureType.isReinvestPrincipal()) {
|
// the deposit rolls over. Nothing moves: the whole matured balance simply becomes the new
|
||||||
reInvestAmount = account.getDepositAmount();
|
// term's principal, so there is no withdrawal, no deposit and no journal entry for the
|
||||||
} else {
|
// rollover itself -- which also removes the transfer-suspense round trip the old
|
||||||
reInvestAmount = account.getAccountBalance();
|
// close-and-reopen flow needed.
|
||||||
}
|
final BigDecimal closingPrincipal = account.getDepositAmount();
|
||||||
|
final BigDecimal maturedBalance = account.getAccountBalance();
|
||||||
|
final BigDecimal interestEarned = maturedBalance == null || closingPrincipal == null ? null
|
||||||
|
: maturedBalance.subtract(closingPrincipal);
|
||||||
|
|
||||||
|
// The active CBS base interest-rate version for this FD type: the same rate card the Base
|
||||||
|
// Interest Rates screen shows and the account-opening wizard prices from. Used for both the
|
||||||
|
// new term's slabs and its senior-citizen premium, so screen and account agree.
|
||||||
|
final FdInterestRateEffectiveData activeBaseRates = account.fdType() == null ? null
|
||||||
|
: this.fdBaseInterestRateReadPlatformService.retrieveActiveInterestRatesByType(account.fdType()).stream().findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
final BigDecimal currentSeniorCitizenRate = account.isSeniorCitizen()
|
||||||
|
? seniorCitizenRateFor(activeBaseRates, account.depositPeriod(), account.depositPeriodFrequency())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Captured before renewInPlace overwrites the account's chart, so the closing term's full
|
||||||
|
// rate-by-tenure chart (including its senior-citizen premiums) can be archived rather than lost
|
||||||
|
// -- renewInPlace mutates the same chart row every time, it does not create a new one.
|
||||||
|
final String closingChartName = account.currentChartName();
|
||||||
|
final List<DepositAccountInterestRateChart.SlabSnapshot> closingChartSlabs = account.currentChartSlabFields();
|
||||||
|
// Likewise captured before renewInPlace overwrites the account's own rate/tenure with the new
|
||||||
|
// term's -- needed only if this is the closing term's first-ever history row (see
|
||||||
|
// recordInPlaceRenewal), so that row records what this term actually ran at instead of blank.
|
||||||
|
final BigDecimal closingInterestRate = account.getNominalAnnualInterestRate();
|
||||||
|
final Integer closingTenure = account.depositPeriod();
|
||||||
|
final Integer closingTenureFrequencyEnum = account.depositPeriodFrequency();
|
||||||
|
|
||||||
|
account.renewInPlace(maturedBalance, closedDate, currentSeniorCitizenRate, baseChartFieldsOf(activeBaseRates),
|
||||||
|
baseChartSlabsOf(activeBaseRates, account.getCurrency().getCode()));
|
||||||
|
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||||
|
financialYearBeginningMonth);
|
||||||
|
this.savingsAccountRepository.save(account);
|
||||||
|
|
||||||
|
recordInPlaceRenewal(account, closingPrincipal, interestEarned, maturedBalance, maturedBalance, closedDate,
|
||||||
|
FixedDepositRenewalHistory.RENEWAL_TYPE_AUTO, user, closingChartName, closingChartSlabs, closingInterestRate,
|
||||||
|
closingTenure, closingTenureFrequencyEnum);
|
||||||
|
changes.put("renewedInPlace", Boolean.TRUE);
|
||||||
|
changes.put("renewalCycleNo", account.renewalCycleNo());
|
||||||
|
|
||||||
|
} else if (onClosureType.isReinvest()) {
|
||||||
|
// "Re-invest principal only" still closes and reopens, because only the principal carries
|
||||||
|
// forward and the interest has to leave the deposit -- which needs a payout destination that
|
||||||
|
// an in-place renewal has nowhere to send. Left on the original flow rather than guessing.
|
||||||
|
final BigDecimal maturedBalance = account.getAccountBalance();
|
||||||
|
final BigDecimal reInvestAmount = account.getDepositAmount();
|
||||||
ExternalId externalId = this.externalIdFactory.create();
|
ExternalId externalId = this.externalIdFactory.create();
|
||||||
|
|
||||||
FixedDepositAccount reinvestedDeposit = account.reInvest(reInvestAmount, externalId);
|
FixedDepositAccount reinvestedDeposit = account.reInvest(reInvestAmount, externalId);
|
||||||
@@ -276,18 +364,26 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
|
|
||||||
this.savingsAccountRepository.saveAndFlush(reinvestedDeposit);
|
this.savingsAccountRepository.saveAndFlush(reinvestedDeposit);
|
||||||
autoGenerateAccountNumber(reinvestedDeposit);
|
autoGenerateAccountNumber(reinvestedDeposit);
|
||||||
|
// Renewal moves the balance internally into the new deposit -- no cash leaves the branch --
|
||||||
|
// so this leg is posted as an account transfer (against the transfer-suspense GL) rather
|
||||||
|
// than as a counter withdrawal, which would wrongly credit the cash/vault account.
|
||||||
|
final boolean isInternalTransferLeg = true;
|
||||||
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, reInvestAmount, paymentDetail,
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, reInvestAmount, paymentDetail,
|
||||||
false, isRegularTransaction);
|
false, isRegularTransaction, isInternalTransferLeg);
|
||||||
savingsTransactionId = withdrawal.getId();
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
// handleWithdrawal() posts this withdrawal's own journal entries, so it has to be marked as
|
||||||
|
// already accounted for -- exactly as the transfer-to-savings branch below does -- or the
|
||||||
|
// postJournalEntries() call at the end of this method posts it a second time.
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
|
||||||
if (onClosureType.isReinvestPrincipalAndInterest()) {
|
|
||||||
account.updateClosedStatus();
|
|
||||||
account.updateOnAccountClosureStatus(onClosureType);
|
|
||||||
}
|
|
||||||
changes.put("reinvestedDepositId", reinvestedDeposit.getId());
|
changes.put("reinvestedDepositId", reinvestedDeposit.getId());
|
||||||
reinvestedDeposit.approveAndActivateApplication(closedDate, user);
|
reinvestedDeposit.approveAndActivateApplication(closedDate, user);
|
||||||
this.savingsAccountRepository.save(reinvestedDeposit);
|
this.savingsAccountRepository.save(reinvestedDeposit);
|
||||||
|
|
||||||
|
// Reached from the maturity job processing the account's standing maturity instruction.
|
||||||
|
recordRenewal(account, reinvestedDeposit, maturedBalance, reInvestAmount, closedDate,
|
||||||
|
FixedDepositRenewalHistory.RENEWAL_TYPE_AUTO, user);
|
||||||
|
|
||||||
} else if (onClosureType.isTransferToSavings()) {
|
} else if (onClosureType.isTransferToSavings()) {
|
||||||
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
|
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
|
||||||
DepositAccountType.SAVINGS_DEPOSIT);
|
DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
@@ -304,6 +400,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
||||||
paymentDetail, false, isRegularTransaction);
|
paymentDetail, false, isRegularTransaction);
|
||||||
savingsTransactionId = withdrawal.getId();
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(!processMaturityInstructionOnly)
|
// if(!processMaturityInstructionOnly)
|
||||||
@@ -429,6 +526,274 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the closing term's full rate-by-tenure chart -- each slab together with its senior-citizen premium --
|
||||||
|
* against its {@link FixedDepositRenewalHistory} row.
|
||||||
|
* <p>
|
||||||
|
* Slabs missing a rate or a from-period are skipped -- they cannot describe a tenure band. Writes nothing when the
|
||||||
|
* closing term had no chart (e.g. a plain flat-rate deposit), rather than persisting an empty, meaningless archive.
|
||||||
|
*/
|
||||||
|
private void archiveChartSlabs(final Long fdRenewalHistoryId, final String chartName,
|
||||||
|
final List<DepositAccountInterestRateChart.SlabSnapshot> snapshots, final String currencyCode) {
|
||||||
|
if (snapshots == null || snapshots.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final DepositAccountInterestRateChart.SlabSnapshot snapshot : snapshots) {
|
||||||
|
final InterestRateChartSlabFields fields = snapshot.getFields();
|
||||||
|
if (fields.annualInterestRate() == null || fields.fromPeriod() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.fixedDepositRenewalChartSlabHistoryRepository
|
||||||
|
.save(FixedDepositRenewalChartSlabHistory.create(fdRenewalHistoryId, chartName, fields.description(),
|
||||||
|
fields.periodType(), fields.fromPeriod(), fields.toPeriod(), fields.getAmountRangeFrom(),
|
||||||
|
fields.getAmountRangeTo(), fields.annualInterestRate(), currencyCode, snapshot.getSeniorCitizenRate()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chart identity for the account snapshot, so it names the base rate version it was priced from. Package-private
|
||||||
|
* and static (no instance state used) so {@link DepositAccountAssembler} can reuse the exact same mapping when a
|
||||||
|
* new FD account is first priced from this master, instead of duplicating the logic.
|
||||||
|
*/
|
||||||
|
static InterestRateChartFields baseChartFieldsOf(final FdInterestRateEffectiveData baseRates) {
|
||||||
|
// name and from_date are NOT NULL on the embeddable. A base version missing either would abort the
|
||||||
|
// whole renewal (or account creation) with a constraint violation, so treat it as "no identity to
|
||||||
|
// apply" instead: refreshFromSlabSnapshots keeps the chart's existing fields when handed null, and
|
||||||
|
// the slabs -- the part that actually prices the deposit -- still get applied.
|
||||||
|
if (baseRates == null || baseRates.getVersionLabel() == null || baseRates.getEffectiveFromDate() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final boolean isPrimaryGroupingByAmount = false; // the base rate card bands by tenure, never by amount
|
||||||
|
return InterestRateChartFields.createNew(baseRates.getVersionLabel(), baseRates.getRemarks(), baseRates.getEffectiveFromDate(),
|
||||||
|
baseRates.getEffectiveToDate(), isPrimaryGroupingByAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the base rate card's slabs into the shape an account chart stores, each paired with its senior-citizen
|
||||||
|
* premium so that figure is persisted on the account (and later archived) rather than only ever being looked up live.
|
||||||
|
* <p>
|
||||||
|
* The base master bands purely by tenure, so amount ranges are left unset -- which
|
||||||
|
* {@code InterestRateChartSlabFields.isAmountBetween} then treats as "any amount", exactly the intent. Slabs with no
|
||||||
|
* from-period are dropped, since a slab that does not say where it starts cannot be matched against a tenure.
|
||||||
|
*/
|
||||||
|
static Collection<DepositAccountInterestRateChart.SlabSnapshot> baseChartSlabsOf(final FdInterestRateEffectiveData baseRates,
|
||||||
|
final String currencyCode) {
|
||||||
|
if (baseRates == null || baseRates.getSlabs() == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
final List<DepositAccountInterestRateChart.SlabSnapshot> snapshots = new ArrayList<>();
|
||||||
|
for (final FdInterestRateSlabData slab : baseRates.getSlabs()) {
|
||||||
|
if (slab.getFromPeriod() == null || slab.getAnnualInterestRate() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final InterestRateChartSlabFields fields = InterestRateChartSlabFields.createNew(slab.getDescription(),
|
||||||
|
SavingsPeriodFrequencyType.fromInt(slab.getPeriodTypeEnum()), slab.getFromPeriod(), slab.getToPeriod(), null, null,
|
||||||
|
slab.getAnnualInterestRate(), currencyCode);
|
||||||
|
snapshots.add(new DepositAccountInterestRateChart.SlabSnapshot(fields, slab.getSeniorCitizenRate()));
|
||||||
|
}
|
||||||
|
return snapshots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The senior-citizen premium from the active base rate card for a given tenure.
|
||||||
|
* <p>
|
||||||
|
* The tenure and the slab bounds are converted to a common unit before comparing, so a rate card published in years
|
||||||
|
* still matches a deposit taken for a number of months. Conversion stays inside a unit family -- days/weeks, or
|
||||||
|
* months/years -- because only those are exact; a days-based card is therefore still never matched against a
|
||||||
|
* months-based tenure, since "1 month" is not a fixed number of days and guessing would land the deposit in the
|
||||||
|
* wrong band. Null when no slab covers the tenure or the matching slab carries no premium, which leaves the
|
||||||
|
* account's existing rate untouched rather than silently dropping the depositor's premium.
|
||||||
|
*/
|
||||||
|
private BigDecimal seniorCitizenRateFor(final FdInterestRateEffectiveData baseRates, final Integer tenure,
|
||||||
|
final Integer tenurePeriodTypeEnum) {
|
||||||
|
if (baseRates == null || baseRates.getSlabs() == null || tenure == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Integer tenureInBaseUnit = inFamilyBaseUnit(tenure, tenurePeriodTypeEnum);
|
||||||
|
if (tenureInBaseUnit == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final FdInterestRateSlabData slab : baseRates.getSlabs()) {
|
||||||
|
if (!sameUnitFamily(tenurePeriodTypeEnum, slab.getPeriodTypeEnum())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Integer slabFrom = inFamilyBaseUnit(slab.getFromPeriod(), slab.getPeriodTypeEnum());
|
||||||
|
if (slabFrom == null || tenureInBaseUnit < slabFrom) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (slab.getToPeriod() != null) {
|
||||||
|
final Integer slabTo = inFamilyBaseUnit(slab.getToPeriod(), slab.getPeriodTypeEnum());
|
||||||
|
if (slabTo == null || tenureInBaseUnit > slabTo) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final BigDecimal rate = slab.getSeniorCitizenRate();
|
||||||
|
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code value} restated in the finest unit of its own family -- days for DAYS/WEEKS, months for MONTHS/YEARS -- so
|
||||||
|
* two periods written in different but related units can be compared without losing precision. Null for an unknown
|
||||||
|
* unit. Enum codes are {@link SavingsPeriodFrequencyType}: DAYS(0), WEEKS(1), MONTHS(2), YEARS(3).
|
||||||
|
*/
|
||||||
|
private Integer inFamilyBaseUnit(final Integer value, final Integer periodTypeEnum) {
|
||||||
|
if (value == null || periodTypeEnum == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return switch (SavingsPeriodFrequencyType.fromInt(periodTypeEnum)) {
|
||||||
|
case DAYS, MONTHS -> value;
|
||||||
|
case WEEKS -> value * 7;
|
||||||
|
case YEARS -> value * 12;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether two period types convert exactly into one another. Days and weeks do; months and years do. Across those
|
||||||
|
* two families they do not, because a month is not a fixed number of days -- so a days-based rate card is never
|
||||||
|
* matched against a months-based tenure rather than guessing and landing the deposit in the wrong band.
|
||||||
|
*/
|
||||||
|
private boolean sameUnitFamily(final Integer firstPeriodTypeEnum, final Integer secondPeriodTypeEnum) {
|
||||||
|
if (firstPeriodTypeEnum == null || secondPeriodTypeEnum == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isDayFamily(firstPeriodTypeEnum) == isDayFamily(secondPeriodTypeEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDayFamily(final Integer periodTypeEnum) {
|
||||||
|
final SavingsPeriodFrequencyType type = SavingsPeriodFrequencyType.fromInt(periodTypeEnum);
|
||||||
|
return type == SavingsPeriodFrequencyType.DAYS || type == SavingsPeriodFrequencyType.WEEKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a renewal that happened on the SAME account, where there is no second account row to describe the new term.
|
||||||
|
* <p>
|
||||||
|
* Called after {@link FixedDepositAccount#renewInPlace}, which has already advanced the account's cycle number,
|
||||||
|
* principal and maturity date -- so the closing term's figures have to be passed in rather than read back off the
|
||||||
|
* account, where they no longer exist. Every row for one FD shares that FD's single account id and account number;
|
||||||
|
* they are told apart by renewal_no, which is what makes this a one-to-many history of one account.
|
||||||
|
*
|
||||||
|
* @param closingChartName
|
||||||
|
* the name of the chart the closing term was priced on, captured before renewInPlace overwrote it; null if
|
||||||
|
* unavailable
|
||||||
|
* @param closingChartSlabs
|
||||||
|
* the closing term's full rate-by-tenure chart, likewise captured before it was overwritten. Archived
|
||||||
|
* against the closing term's row so its whole rate card remains queryable, not only the single effective
|
||||||
|
* rate {@link FixedDepositRenewalHistory} records.
|
||||||
|
* @param closingInterestRate
|
||||||
|
* the closing term's own {@code nominalAnnualInterestRate}, captured before renewInPlace overwrote it --
|
||||||
|
* needed only when this term has no existing row yet (see below), since the account itself no longer
|
||||||
|
* carries it once renewInPlace has run.
|
||||||
|
* @param closingTenure
|
||||||
|
* the closing term's own deposit period, captured the same way and needed for the same reason.
|
||||||
|
* @param closingTenureFrequencyEnum
|
||||||
|
* the closing term's own deposit period frequency, captured the same way and needed for the same reason.
|
||||||
|
*/
|
||||||
|
private void recordInPlaceRenewal(final FixedDepositAccount account, final BigDecimal closingPrincipal,
|
||||||
|
final BigDecimal closingInterestEarned, final BigDecimal maturedBalance, final BigDecimal renewedAmount,
|
||||||
|
final LocalDate renewedOn, final String renewalType, final AppUser user, final String closingChartName,
|
||||||
|
final List<DepositAccountInterestRateChart.SlabSnapshot> closingChartSlabs, final BigDecimal closingInterestRate,
|
||||||
|
final Integer closingTenure, final Integer closingTenureFrequencyEnum) {
|
||||||
|
|
||||||
|
// The CHAIN's number, not this account's own. They differ for an FD that was renewed under the old
|
||||||
|
// close-and-reopen model before renewing in place: its rows are keyed on the original account's
|
||||||
|
// number, so keying new rows on this account's number would split the history in two and re-create
|
||||||
|
// a duplicate row for the term that already has one. fdAccountNumber() falls back to this account's
|
||||||
|
// own number when there is no chain, which is what the read side resolves to as well.
|
||||||
|
final String fdAccountNumber = account.fdAccountNumber();
|
||||||
|
final int newCycleNo = account.renewalCycleNo();
|
||||||
|
final Long createdById = user == null ? null : user.getId();
|
||||||
|
|
||||||
|
final List<FixedDepositRenewalHistory> existingRows = this.fixedDepositRenewalHistoryRepository
|
||||||
|
.findByFdAccountNumberOrderByRenewalNoAsc(fdAccountNumber);
|
||||||
|
FixedDepositRenewalHistory closingRow = existingRows.stream()
|
||||||
|
.filter(row -> Integer.valueOf(newCycleNo - 1).equals(row.getRenewalNo())).findFirst().orElse(null);
|
||||||
|
|
||||||
|
// The term that just closed has no row when this FD renews for the first time since the history
|
||||||
|
// table existed. Written now, from the figures captured before the rollover, so the history
|
||||||
|
// always begins with the term the customer originally opened -- rate and tenure included, rather
|
||||||
|
// than left blank, since the caller captured them before renewInPlace overwrote the account with
|
||||||
|
// the new term's own values.
|
||||||
|
if (closingRow == null) {
|
||||||
|
closingRow = this.fixedDepositRenewalHistoryRepository.saveAndFlush(FixedDepositRenewalHistory.create(fdAccountNumber,
|
||||||
|
account.getId(), account.getId(), null, null, newCycleNo - 1, closingPrincipal, null, maturedBalance,
|
||||||
|
closingPrincipal, closingInterestRate, account.getActivationDate(), renewedOn, closingTenure,
|
||||||
|
closingTenureFrequencyEnum, renewalType, account.getActivationDate(), createdById));
|
||||||
|
}
|
||||||
|
closingRow.recordOutcome(closingInterestEarned, maturedBalance);
|
||||||
|
closingRow.markMatured();
|
||||||
|
this.fixedDepositRenewalHistoryRepository.save(closingRow);
|
||||||
|
|
||||||
|
archiveChartSlabs(closingRow.getId(), closingChartName, closingChartSlabs, account.getCurrency().getCode());
|
||||||
|
|
||||||
|
// The new term's own interest/maturity stay unset until it closes in turn.
|
||||||
|
this.fixedDepositRenewalHistoryRepository.save(FixedDepositRenewalHistory.create(fdAccountNumber, account.getId(),
|
||||||
|
account.getId(), null, closingRow.getId(), newCycleNo, renewedAmount, null, null, renewedAmount,
|
||||||
|
account.getNominalAnnualInterestRate(), renewedOn, account.maturityDate(), account.depositPeriod(),
|
||||||
|
account.depositPeriodFrequency(), renewalType, renewedOn, createdById));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the renewal that started {@code newTerm}, and closes off the previous term's history row.
|
||||||
|
* <p>
|
||||||
|
* Each term of an FD runs as its own deposit instance so it keeps an immutable ledger, which means the rollover event
|
||||||
|
* itself -- what the previous term earned, what carried forward, at what rate, automatically or by whom -- is not
|
||||||
|
* recoverable from the account rows alone. This writes it down explicitly.
|
||||||
|
* <p>
|
||||||
|
* The chain's first term is backfilled here too when it has no row yet: an FD opened before this table existed only
|
||||||
|
* gets discovered at its first renewal, and without its opening row the history would start at "Renewal 1" with no
|
||||||
|
* original.
|
||||||
|
*/
|
||||||
|
private void recordRenewal(final FixedDepositAccount previousTerm, final FixedDepositAccount newTerm,
|
||||||
|
final BigDecimal maturityAmount, final BigDecimal renewedAmount, final LocalDate renewedOn, final String renewalType,
|
||||||
|
final AppUser user) {
|
||||||
|
|
||||||
|
final String fdAccountNumber = previousTerm.fdAccountNumber();
|
||||||
|
final Long originalAccountId = this.fixedDepositRenewalHistoryRepository
|
||||||
|
.findByFdAccountNumberOrderByRenewalNoAsc(fdAccountNumber).stream().findFirst()
|
||||||
|
.map(FixedDepositRenewalHistory::getOriginalSavingsAccountId)
|
||||||
|
.orElseGet(() -> previousTerm.reinvestedFromSavingsId() == null ? previousTerm.getId() : null);
|
||||||
|
|
||||||
|
FixedDepositRenewalHistory previousRow = this.fixedDepositRenewalHistoryRepository
|
||||||
|
.findBySavingsAccountIdOrderByRenewalNoDesc(previousTerm.getId()).stream().findFirst().orElse(null);
|
||||||
|
|
||||||
|
// Backfill the opening term when the chain started before this table existed, so the history
|
||||||
|
// always begins with the account the customer actually opened.
|
||||||
|
if (previousRow == null && originalAccountId != null && originalAccountId.equals(previousTerm.getId())) {
|
||||||
|
previousRow = this.fixedDepositRenewalHistoryRepository.saveAndFlush(FixedDepositRenewalHistory.create(fdAccountNumber,
|
||||||
|
originalAccountId, previousTerm.getId(), null, null, previousTerm.renewalCycleNo(),
|
||||||
|
previousTerm.getDepositAmount(), null, previousTerm.maturityAmount(), previousTerm.getDepositAmount(),
|
||||||
|
previousTerm.getNominalAnnualInterestRate(), previousTerm.getActivationDate(), previousTerm.maturityDate(),
|
||||||
|
previousTerm.depositPeriod(), previousTerm.depositPeriodFrequency(), renewalType, previousTerm.getActivationDate(),
|
||||||
|
user == null ? null : user.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// What the closing term actually earned. maturityAmount is passed in rather than read off the
|
||||||
|
// account because the caller has already withdrawn the balance out of it by this point, which
|
||||||
|
// would make it read as zero. Recorded on the CLOSING term's own row -- every row states its own
|
||||||
|
// term's outcome, so a reader never has to look at the next row to find out how one term did.
|
||||||
|
final BigDecimal previousPrincipal = previousTerm.getDepositAmount();
|
||||||
|
final BigDecimal interestEarned = maturityAmount == null || previousPrincipal == null ? null
|
||||||
|
: maturityAmount.subtract(previousPrincipal);
|
||||||
|
|
||||||
|
if (previousRow != null) {
|
||||||
|
previousRow.recordOutcome(interestEarned, maturityAmount);
|
||||||
|
previousRow.markMatured();
|
||||||
|
this.fixedDepositRenewalHistoryRepository.save(previousRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The new term's own interest/maturity stay unset until it closes in turn.
|
||||||
|
this.fixedDepositRenewalHistoryRepository.save(FixedDepositRenewalHistory.create(fdAccountNumber,
|
||||||
|
originalAccountId == null ? previousTerm.getId() : originalAccountId, newTerm.getId(), previousTerm.getId(),
|
||||||
|
previousRow == null ? null : previousRow.getId(), newTerm.renewalCycleNo(), renewedAmount, null, null, renewedAmount,
|
||||||
|
newTerm.getNominalAnnualInterestRate(), renewedOn, newTerm.maturityDate(), newTerm.depositPeriod(),
|
||||||
|
newTerm.depositPeriodFrequency(), renewalType, renewedOn, user == null ? null : user.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
|
public Long handleFDAccountPreMatureClosure(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
|
||||||
@@ -474,6 +839,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
||||||
paymentDetail, false, isRegularTransaction);
|
paymentDetail, false, isRegularTransaction);
|
||||||
savingsTransactionId = withdrawal.getId();
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
// handleWithdrawal() posts this withdrawal's journal entries itself, so mark it as already
|
||||||
|
// accounted for or the postJournalEntries() call below posts it a second time.
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
account.prematureClosure(user, command, changes);
|
account.prematureClosure(user, command, changes);
|
||||||
@@ -484,6 +852,238 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
return savingsTransactionId;
|
return savingsTransactionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A staff-triggered, always-available alternative to Premature Close (Active-only, no reinvest) and Close
|
||||||
|
* (Matured-only): works on an FD in either state, and adds a Partial Reinvest option neither of those two support
|
||||||
|
* -- part of the matured amount renews on this same account via {@link FixedDepositAccount#renewInPlace}, the rest
|
||||||
|
* transfers out to a savings account picked at the moment of the request. Full reinvest here also goes through
|
||||||
|
* {@code renewInPlace} rather than the legacy close-and-reopen {@link FixedDepositAccount#reInvest}, which is what
|
||||||
|
* the existing manual Close command still uses today.
|
||||||
|
* <p>
|
||||||
|
* Deliberately independent of {@link #handleFDAccountClosure} and {@link #handleFDAccountPreMatureClosure} --
|
||||||
|
* neither of those two methods is touched, so the existing Close/Premature Close buttons keep behaving exactly as
|
||||||
|
* before.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public Long handleFDAccountPayment(final FixedDepositAccount account, final PaymentDetail paymentDetail, final AppUser user,
|
||||||
|
final JsonCommand command, final Map<String, Object> changes) {
|
||||||
|
|
||||||
|
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||||
|
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||||
|
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||||
|
|
||||||
|
boolean isRegularTransaction = false;
|
||||||
|
boolean isAccountTransfer = false;
|
||||||
|
final boolean isPreMatureClosure = false;
|
||||||
|
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||||
|
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||||
|
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||||
|
|
||||||
|
final MathContext mc = MathContext.DECIMAL64;
|
||||||
|
final Locale locale = command.extractLocale();
|
||||||
|
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
||||||
|
final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
|
||||||
|
Long savingsTransactionId = null;
|
||||||
|
|
||||||
|
// account.isMatured() only reflects whether the overnight scheduler has already flipped this
|
||||||
|
// account's status -- not whether the customer actually showed up before or after the maturity
|
||||||
|
// date. A customer walking in ON the maturity date (or any day after) is not premature just
|
||||||
|
// because that job hasn't run yet today, so the real test is the date, not the status flag.
|
||||||
|
if (!account.isMatured()) {
|
||||||
|
if (!DateUtils.isBefore(closedDate, account.maturityDate())) {
|
||||||
|
// Genuinely due, the scheduler just hasn't caught up yet -- do exactly what it would:
|
||||||
|
// flip to Matured and post final interest at the full contracted rate, no penalty.
|
||||||
|
account.updateMaturityStatus(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
|
} else {
|
||||||
|
// The three closing closure types re-check this themselves inside prematureClosure()
|
||||||
|
// below, but the two reinvest branches never call it (they keep the account open on the
|
||||||
|
// same term), so it is enforced once, up front, for every genuinely-premature Payment --
|
||||||
|
// a locked-in FD (e.g. a Tax Saver with its statutory lock-in) cannot be touched early via
|
||||||
|
// this action either.
|
||||||
|
if (account.isLockedOnDate(closedDate)) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.fixeddeposit.payment.lockin.period.not.elapsed",
|
||||||
|
"This account is still within its lock-in period and cannot be processed before it elapses.", closedDate);
|
||||||
|
}
|
||||||
|
// Same penalty-aware interest calculation prematureClosure() already relies on, so a
|
||||||
|
// genuinely early Payment settles interest exactly as a Premature Close would.
|
||||||
|
account.postPreMaturityInterest(closedDate, true, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as the closure and maturity paths do: the legacy reinvest branch's reInvest() reads
|
||||||
|
// getClosedOnDate() to pick the new account's chart and submitted date, so it has to be set
|
||||||
|
// before branching. renewInPlace() clears it again for the branches that keep the account open.
|
||||||
|
account.setClosedOnDate(closedDate);
|
||||||
|
|
||||||
|
// Re-read after the block above: updateMaturityStatus() may just have flipped it, so this now
|
||||||
|
// correctly decides close() (needs Matured) vs prematureClosure() (needs Active) either way.
|
||||||
|
final boolean alreadyMatured = account.isMatured();
|
||||||
|
|
||||||
|
final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName);
|
||||||
|
final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId);
|
||||||
|
|
||||||
|
if (onClosureType.isReinvestPrincipalAndInterest() || onClosureType.isPartialReinvest()) {
|
||||||
|
// These two branches keep the account open, so they never reach close()/prematureClosure() and
|
||||||
|
// would otherwise accept any date at all -- including one behind already-posted interest, which
|
||||||
|
// the renewal would then turn into a pivot that reverses and re-posts it.
|
||||||
|
account.validateRenewalDate(closedDate);
|
||||||
|
|
||||||
|
final BigDecimal closingPrincipal = account.getDepositAmount();
|
||||||
|
final BigDecimal maturedBalance = account.getAccountBalance();
|
||||||
|
final BigDecimal interestEarned = maturedBalance == null || closingPrincipal == null ? null
|
||||||
|
: maturedBalance.subtract(closingPrincipal);
|
||||||
|
|
||||||
|
final BigDecimal renewedAmount;
|
||||||
|
if (onClosureType.isPartialReinvest()) {
|
||||||
|
renewedAmount = command.bigDecimalValueOfParameterNamed(reinvestAmountParamName);
|
||||||
|
if (renewedAmount == null || renewedAmount.compareTo(BigDecimal.ZERO) <= 0
|
||||||
|
|| renewedAmount.compareTo(maturedBalance) >= 0) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.fixeddeposit.partial.reinvest.amount.invalid",
|
||||||
|
"The reinvest amount must be greater than zero and less than the matured balance.", renewedAmount);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renewedAmount = maturedBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
final FdInterestRateEffectiveData activeBaseRates = account.fdType() == null ? null
|
||||||
|
: this.fdBaseInterestRateReadPlatformService.retrieveActiveInterestRatesByType(account.fdType()).stream().findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
final BigDecimal currentSeniorCitizenRate = account.isSeniorCitizen()
|
||||||
|
? seniorCitizenRateFor(activeBaseRates, account.depositPeriod(), account.depositPeriodFrequency())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
final String closingChartName = account.currentChartName();
|
||||||
|
final List<DepositAccountInterestRateChart.SlabSnapshot> closingChartSlabs = account.currentChartSlabFields();
|
||||||
|
// Likewise captured before renewInPlace overwrites the account's own rate/tenure with the new
|
||||||
|
// term's -- needed only if this is the closing term's first-ever history row (see
|
||||||
|
// recordInPlaceRenewal), so that row records what this term actually ran at instead of blank.
|
||||||
|
final BigDecimal closingInterestRate = account.getNominalAnnualInterestRate();
|
||||||
|
final Integer closingTenure = account.depositPeriod();
|
||||||
|
final Integer closingTenureFrequencyEnum = account.depositPeriodFrequency();
|
||||||
|
|
||||||
|
account.renewInPlace(renewedAmount, closedDate, currentSeniorCitizenRate, baseChartFieldsOf(activeBaseRates),
|
||||||
|
baseChartSlabsOf(activeBaseRates, account.getCurrency().getCode()));
|
||||||
|
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||||
|
financialYearBeginningMonth);
|
||||||
|
this.savingsAccountRepository.save(account);
|
||||||
|
|
||||||
|
recordInPlaceRenewal(account, closingPrincipal, interestEarned, maturedBalance, renewedAmount, closedDate,
|
||||||
|
FixedDepositRenewalHistory.RENEWAL_TYPE_MANUAL, user, closingChartName, closingChartSlabs, closingInterestRate,
|
||||||
|
closingTenure, closingTenureFrequencyEnum);
|
||||||
|
changes.put("renewedInPlace", Boolean.TRUE);
|
||||||
|
changes.put("renewalCycleNo", account.renewalCycleNo());
|
||||||
|
|
||||||
|
if (onClosureType.isPartialReinvest()) {
|
||||||
|
// The kept portion never left the account -- renewInPlace() just relabels it as the new
|
||||||
|
// term's principal -- so only the remainder is an actual money movement, posted the same
|
||||||
|
// way the Transfer to Savings branch below posts its full-balance transfer.
|
||||||
|
final BigDecimal remainder = maturedBalance.subtract(renewedAmount);
|
||||||
|
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
|
||||||
|
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
|
||||||
|
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, remainder, PortfolioAccountType.SAVINGS,
|
||||||
|
PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null, null, null, null,
|
||||||
|
AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null, toSavingsAccount, account,
|
||||||
|
isAccountTransfer, isExceptionForBalanceCheck);
|
||||||
|
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (onClosureType.isReinvestPrincipal()) {
|
||||||
|
// Legacy close-and-reopen, same shape as handleFDAccountClosure()'s isReinvest() branch --
|
||||||
|
// the interest has to leave the deposit, which needs a payout destination an in-place renewal
|
||||||
|
// has nowhere to send.
|
||||||
|
final BigDecimal maturedBalance = account.getAccountBalance();
|
||||||
|
final BigDecimal reInvestAmount = account.getDepositAmount();
|
||||||
|
final ExternalId externalId = this.externalIdFactory.create();
|
||||||
|
|
||||||
|
final FixedDepositAccount reinvestedDeposit = account.reInvest(reInvestAmount, externalId);
|
||||||
|
this.depositAccountAssembler.assignSavingAccountHelpers(reinvestedDeposit);
|
||||||
|
reinvestedDeposit.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
|
||||||
|
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||||
|
|
||||||
|
this.savingsAccountRepository.saveAndFlush(reinvestedDeposit);
|
||||||
|
autoGenerateAccountNumber(reinvestedDeposit);
|
||||||
|
final boolean isInternalTransferLeg = true;
|
||||||
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, reInvestAmount, paymentDetail,
|
||||||
|
false, isRegularTransaction, isInternalTransferLeg);
|
||||||
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
|
||||||
|
changes.put("reinvestedDepositId", reinvestedDeposit.getId());
|
||||||
|
reinvestedDeposit.approveAndActivateApplication(closedDate, user);
|
||||||
|
this.savingsAccountRepository.save(reinvestedDeposit);
|
||||||
|
|
||||||
|
recordRenewal(account, reinvestedDeposit, maturedBalance, reInvestAmount, closedDate,
|
||||||
|
FixedDepositRenewalHistory.RENEWAL_TYPE_MANUAL, user);
|
||||||
|
|
||||||
|
// Only the principal moved into the new deposit -- the interest earned on this term is still
|
||||||
|
// sitting in this account's balance. Pay it out to the chosen savings account now, before
|
||||||
|
// closing, rather than stranding it here with nowhere left to go.
|
||||||
|
final BigDecimal interestRemainder = maturedBalance.subtract(reInvestAmount);
|
||||||
|
if (interestRemainder.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
|
||||||
|
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
|
||||||
|
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, interestRemainder,
|
||||||
|
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null,
|
||||||
|
null, null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
|
||||||
|
toSavingsAccount, account, isAccountTransfer, isExceptionForBalanceCheck);
|
||||||
|
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alreadyMatured) {
|
||||||
|
account.close(user, command, changes);
|
||||||
|
} else {
|
||||||
|
account.prematureClosure(user, command, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (onClosureType.isTransferToSavings()) {
|
||||||
|
final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName);
|
||||||
|
final String transferDescription = command.stringValueOfParameterNamed(transferDescriptionParamName);
|
||||||
|
final SavingsAccount toSavingsAccount = this.depositAccountAssembler.assembleFrom(toSavingsId,
|
||||||
|
DepositAccountType.SAVINGS_DEPOSIT);
|
||||||
|
final boolean isExceptionForBalanceCheck = false;
|
||||||
|
final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(closedDate, account.getAccountBalance(),
|
||||||
|
PortfolioAccountType.SAVINGS, PortfolioAccountType.SAVINGS, null, null, transferDescription, locale, fmt, null, null,
|
||||||
|
null, null, null, AccountTransferType.ACCOUNT_TRANSFER.getValue(), null, null, ExternalId.empty(), null,
|
||||||
|
toSavingsAccount, account, isAccountTransfer, isExceptionForBalanceCheck);
|
||||||
|
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
|
||||||
|
if (alreadyMatured) {
|
||||||
|
account.close(user, command, changes);
|
||||||
|
} else {
|
||||||
|
account.prematureClosure(user, command, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(),
|
||||||
|
paymentDetail, false, isRegularTransaction);
|
||||||
|
savingsTransactionId = withdrawal.getId();
|
||||||
|
updateAlreadyPostedTransactions(existingTransactionIds, account);
|
||||||
|
|
||||||
|
if (alreadyMatured) {
|
||||||
|
account.close(user, command, changes);
|
||||||
|
} else {
|
||||||
|
account.prematureClosure(user, command, changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.savingsAccountRepository.save(account);
|
||||||
|
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
|
||||||
|
|
||||||
|
return savingsTransactionId;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public Long handleRDAccountPreMatureClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail,
|
public Long handleRDAccountPreMatureClosure(final RecurringDepositAccount account, final PaymentDetail paymentDetail,
|
||||||
@@ -555,14 +1155,19 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAlreadyPostedTransactions(final Set<Long> existingTransactionIds, final SavingsAccount savingsAccount) {
|
private void updateAlreadyPostedTransactions(final Set<Long> existingTransactionIds, final SavingsAccount savingsAccount) {
|
||||||
List<SavingsAccountTransaction> transactions = savingsAccount.getTransactions();
|
// accountTransfersWritePlatformService.transferFunds() already posts accounting for the
|
||||||
int size = transactions.size();
|
// withdrawal it creates on this account (via SavingsAccountDomainServiceJpa#handleWithdrawal),
|
||||||
for (int i = size - 1;; i--) {
|
// so that withdrawal must be added to existingTransactionIds here or the caller's own
|
||||||
SavingsAccountTransaction transaction = transactions.get(i);
|
// postJournalEntries() re-derives and re-posts it a second time. The previous implementation
|
||||||
if (transaction.isWithdrawal() || transaction.isWithdrawalFee()) {
|
// found it by walking the transaction list backwards from the end while the type stayed
|
||||||
|
// withdrawal/withdrawal-fee -- but @OneToMany collections only guarantee @OrderBy ordering on
|
||||||
|
// a fresh fetch, not after an in-memory append, so a same-day interest posting or any other
|
||||||
|
// ordering quirk could put the new withdrawal anywhere in the list and break the walk
|
||||||
|
// silently. Scanning every transaction and checking against the original snapshot is correct
|
||||||
|
// regardless of collection order.
|
||||||
|
for (final SavingsAccountTransaction transaction : savingsAccount.getTransactions()) {
|
||||||
|
if ((transaction.isWithdrawal() || transaction.isWithdrawalFee()) && !existingTransactionIds.contains(transaction.getId())) {
|
||||||
existingTransactionIds.add(transaction.getId());
|
existingTransactionIds.add(transaction.getId());
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+288
-21
@@ -53,6 +53,7 @@ import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
|
|||||||
import org.apache.fineract.portfolio.client.domain.Client;
|
import org.apache.fineract.portfolio.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.group.domain.Group;
|
import org.apache.fineract.portfolio.group.domain.Group;
|
||||||
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChart;
|
||||||
|
import org.apache.fineract.portfolio.interestratechart.domain.InterestRateChartFields;
|
||||||
import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
|
import org.apache.fineract.portfolio.interestratechart.service.InterestRateChartAssembler;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
|
||||||
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
import org.apache.fineract.portfolio.savings.DepositAccountType;
|
||||||
@@ -276,7 +277,9 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
|
|
||||||
public LocalDate calculateMaturityDate() {
|
public LocalDate calculateMaturityDate() {
|
||||||
|
|
||||||
final LocalDate startDate = accountSubmittedOrActivationDate();
|
// The CURRENT term's start, not the account's activation date -- on a renewed FD these differ,
|
||||||
|
// and measuring the term from activation would put maturity in the past.
|
||||||
|
final LocalDate startDate = depositStartDate();
|
||||||
LocalDate maturityDate = null;
|
LocalDate maturityDate = null;
|
||||||
final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
|
final Integer depositPeriod = this.accountTermAndPreClosure.depositPeriod();
|
||||||
switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) {
|
switch (this.accountTermAndPreClosure.depositPeriodFrequencyType()) {
|
||||||
@@ -312,13 +315,19 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
.fromInt(this.interestCalculationDaysInYearType);
|
.fromInt(this.interestCalculationDaysInYearType);
|
||||||
List<LocalDate> postedAsOnTransactionDates = getManualPostingDates();
|
List<LocalDate> postedAsOnTransactionDates = getManualPostingDates();
|
||||||
|
|
||||||
|
// Scoped to the current term, so a renewed FD's projection covers only the term now running --
|
||||||
|
// matching what postInterest() will actually post -- rather than replaying the whole account.
|
||||||
final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
|
final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(
|
||||||
accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth,
|
depositStartDate(), maturityDate, postingPeriodType, financialYearBeginningMonth, postedAsOnTransactionDates);
|
||||||
postedAsOnTransactionDates);
|
|
||||||
|
|
||||||
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
|
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
|
||||||
|
|
||||||
Money periodStartingBalance = Money.zero(currency);
|
// On a first term the opening deposit falls inside the first posting period, so the period can
|
||||||
|
// start from zero and pick the balance up from that transaction. On a renewed term that deposit
|
||||||
|
// is BEFORE the period range and would be missed, leaving the term to earn interest on nothing,
|
||||||
|
// so the term's own principal seeds the opening balance instead.
|
||||||
|
Money periodStartingBalance = isRenewedTerm() ? Money.of(currency, this.accountTermAndPreClosure.depositAmount())
|
||||||
|
: Money.zero(currency);
|
||||||
|
|
||||||
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
|
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
|
||||||
final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure);
|
final BigDecimal interestRateAsFraction = getEffectiveInterestRateAsFraction(mc, maturityDate, isPreMatureClosure);
|
||||||
@@ -435,7 +444,10 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Money activateWithBalance() {
|
public Money activateWithBalance() {
|
||||||
return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount());
|
// Activation never deposits money -- funding happens strictly through a separate deposit
|
||||||
|
// transaction (posted by a clerk) after the account is already active, regardless of the
|
||||||
|
// deposit amount configured on the account's term/pre-closure details.
|
||||||
|
return Money.zero(this.currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges) {
|
public void close(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges) {
|
||||||
@@ -683,6 +695,60 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
return this.accountTermAndPreClosure.maturityAmount();
|
return this.accountTermAndPreClosure.maturityAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stable customer-facing FD number: the account number of the first account in this renewal chain. Falls back to
|
||||||
|
* this account's own number when it has never been renewed, so callers always get a usable FD number.
|
||||||
|
*/
|
||||||
|
public String fdAccountNumber() {
|
||||||
|
final String carried = this.accountTermAndPreClosure.getFdAccountNumber();
|
||||||
|
return carried != null ? carried : getAccountNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 0 for the originally opened term, 1 for the first renewal, and so on. */
|
||||||
|
public Integer renewalCycleNo() {
|
||||||
|
return this.accountTermAndPreClosure.getRenewalCycleNo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer depositPeriod() {
|
||||||
|
return this.accountTermAndPreClosure.depositPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer depositPeriodFrequency() {
|
||||||
|
return this.accountTermAndPreClosure.depositPeriodFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long reinvestedFromSavingsId() {
|
||||||
|
return this.accountTermAndPreClosure.getReinvestedFromSavingsId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the chart currently priced on this account. Read before a renewal overwrites it, so the closing term's
|
||||||
|
* chart identity can be archived rather than lost.
|
||||||
|
*/
|
||||||
|
public String currentChartName() {
|
||||||
|
return this.chart == null ? null : this.chart.chartFields().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The slabs of the chart currently priced on this account -- each paired with its senior-citizen premium -- detached
|
||||||
|
* from the chart's own persistence lifecycle.
|
||||||
|
* <p>
|
||||||
|
* Read before a renewal overwrites the account's chart in place ({@link #renewInPlace}), so the closing term's full
|
||||||
|
* rate-by-tenure breakdown (including the premium) can be archived -- not just the single effective rate the renewal
|
||||||
|
* history records -- rather than being silently deleted when the new term's slabs replace it.
|
||||||
|
*/
|
||||||
|
public List<DepositAccountInterestRateChart.SlabSnapshot> currentChartSlabFields() {
|
||||||
|
return this.chart == null ? java.util.Collections.emptyList() : this.chart.currentSlabSnapshots();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This deposit's FD type (SINGLE / DOUBLE / SPECIAL), which lives on the product rather than the account. Null when
|
||||||
|
* the product is not a Fixed Deposit product or has no type set.
|
||||||
|
*/
|
||||||
|
public String fdType() {
|
||||||
|
return this.product instanceof FixedDepositProduct ? ((FixedDepositProduct) this.product).getFdType() : null;
|
||||||
|
}
|
||||||
|
|
||||||
private Money totalInterestPosted() {
|
private Money totalInterestPosted() {
|
||||||
Money interestPostedToDate = Money.zero(this.currency);
|
Money interestPostedToDate = Money.zero(this.currency);
|
||||||
for (final SavingsAccountTransaction transaction : this.transactions) {
|
for (final SavingsAccountTransaction transaction : this.transactions) {
|
||||||
@@ -712,11 +778,26 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
return actualChanges;
|
return actualChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start of the term currently running, which is what the deposit's tenure and rate must be measured from.
|
||||||
|
* <p>
|
||||||
|
* For an FD that has never been renewed this is the activation date, exactly as before. A renewal advances
|
||||||
|
* startInterestCalculationDate to the renewal date, so from then on the interest-rate chart slab is chosen by THIS
|
||||||
|
* term's tenure rather than the cumulative tenure since the account was opened, and maturity is measured from the
|
||||||
|
* renewal rather than from the original activation.
|
||||||
|
*/
|
||||||
private LocalDate depositStartDate() {
|
private LocalDate depositStartDate() {
|
||||||
// TODO: Support to add deposit start date which can be a date after
|
final LocalDate currentTermStartDate = getStartInterestCalculationDate();
|
||||||
// account activation date.
|
return currentTermStartDate != null ? currentTermStartDate : accountSubmittedOrActivationDate();
|
||||||
final LocalDate depositStartDate = accountSubmittedOrActivationDate();
|
}
|
||||||
return depositStartDate;
|
|
||||||
|
/**
|
||||||
|
* True once this FD has been renewed at least once, i.e. the term now running started later than the account did.
|
||||||
|
* Everything keyed off this keeps its original, pre-renewal behaviour for an FD still on its first term.
|
||||||
|
*/
|
||||||
|
private boolean isRenewedTerm() {
|
||||||
|
final LocalDate termStart = getStartInterestCalculationDate();
|
||||||
|
return termStart != null && getActivationDate() != null && !termStart.equals(getActivationDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate interestCalculatedUpto() {
|
private LocalDate interestCalculatedUpto() {
|
||||||
@@ -811,28 +892,142 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
return this.accountTermAndPreClosure.getTransferToSavingsAccountId();
|
return this.accountTermAndPreClosure.getTransferToSavingsAccountId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolls this FD into a new term on the SAME account, so the customer keeps one account number however many times it
|
||||||
|
* renews. No new account is opened and no money moves: the balance simply carries into the next term.
|
||||||
|
* <p>
|
||||||
|
* What changes is the term itself. startInterestCalculationDate advances to the renewal date, which is what confines
|
||||||
|
* interest posting to the new term and thereby leaves the closing term's postings untouched -- they fall outside the
|
||||||
|
* posting periods the engine now generates, so nothing recalculates or reverses them. The term's principal becomes
|
||||||
|
* {@code renewedAmount}, and every interest-mechanics setting plus the rate chart is re-taken from the product as it
|
||||||
|
* stands today, so a renewal is priced on current terms rather than the ones frozen at account opening.
|
||||||
|
* <p>
|
||||||
|
* The caller must have posted the closing term's interest first, and must record the renewal in the FD's renewal
|
||||||
|
* history so each term's own figures remain reportable.
|
||||||
|
*
|
||||||
|
* @param renewedAmount
|
||||||
|
* what carries into the new term: the full maturity amount, or the principal only
|
||||||
|
* @param renewedOn
|
||||||
|
* the renewal date, which becomes the new term's start
|
||||||
|
* @param currentSeniorCitizenRate
|
||||||
|
* the senior-citizen premium the bank publishes on the renewal date for this FD type and tenure, or null to
|
||||||
|
* keep the rate the account already holds. Only applied to an account already flagged senior-citizen; the
|
||||||
|
* renewal never turns that flag on or off.
|
||||||
|
* @param baseChartFields
|
||||||
|
* identity of the CBS base rate version being applied, or null to keep the existing chart's
|
||||||
|
* @param baseChartSlabs
|
||||||
|
* the slabs of the active CBS base interest-rate version, each paired with its senior-citizen premium -- the
|
||||||
|
* rate card staff maintain and see on the Base Interest Rates screen. When empty or null the account falls
|
||||||
|
* back to the product's own chart, so a product with no base rate version still renews on something.
|
||||||
|
*/
|
||||||
|
public void renewInPlace(final BigDecimal renewedAmount, final LocalDate renewedOn, final BigDecimal currentSeniorCitizenRate,
|
||||||
|
final InterestRateChartFields baseChartFields, final Collection<DepositAccountInterestRateChart.SlabSnapshot> baseChartSlabs) {
|
||||||
|
|
||||||
|
final SavingsProduct product = this.product;
|
||||||
|
|
||||||
|
// Confines interest posting to the new term. Set before recomputing maturity, since maturity is
|
||||||
|
// measured from the current term's start.
|
||||||
|
setStartInterestCalculationDate(renewedOn);
|
||||||
|
|
||||||
|
this.accountTermAndPreClosure.updateDepositAmount(renewedAmount);
|
||||||
|
this.accountTermAndPreClosure.setRenewalCycleNo(this.accountTermAndPreClosure.getRenewalCycleNo() + 1);
|
||||||
|
// Pin the chain number now that this account has renewed at least once, so a non-zero cycle number
|
||||||
|
// is never paired with a blank FD number. fdAccountNumber() resolves to the chain's original number
|
||||||
|
// if there is one and to this account's own otherwise, which is the same value every reader derives.
|
||||||
|
this.accountTermAndPreClosure.setFdAccountNumber(fdAccountNumber());
|
||||||
|
|
||||||
|
// Re-price off the CURRENT product, exactly as opening a fresh deposit today would.
|
||||||
|
this.nominalAnnualInterestRate = product.nominalAnnualInterestRate();
|
||||||
|
this.interestCompoundingPeriodType = product.interestCompoundingPeriodType().getValue();
|
||||||
|
this.interestPostingPeriodType = product.interestPostingPeriodType().getValue();
|
||||||
|
this.interestCalculationType = product.interestCalculationType().getValue();
|
||||||
|
this.interestCalculationDaysInYearType = product.interestCalculationDaysInYearType().getValue();
|
||||||
|
this.withHoldTax = product.withHoldTax();
|
||||||
|
|
||||||
|
// Lock-in is re-taken from the product and re-measured from this renewal, exactly as the legacy
|
||||||
|
// close-and-reopen reInvest() does for the account it creates. Left alone, a renewed term would
|
||||||
|
// carry the lock-in window computed at the original activation -- long expired -- so a product with
|
||||||
|
// a statutory lock-in (a Tax Saver, say) would hold the customer for the first term only.
|
||||||
|
final SavingsPeriodFrequencyType renewedLockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
|
||||||
|
this.lockinPeriodFrequency = product.lockinPeriodFrequency();
|
||||||
|
this.lockinPeriodFrequencyType = renewedLockinPeriodFrequencyType == null ? null
|
||||||
|
: renewedLockinPeriodFrequencyType.getValue();
|
||||||
|
this.lockedInUntilDate = this.lockinPeriodFrequencyType == null || this.lockinPeriodFrequency == null ? null
|
||||||
|
: calculateDateAccountIsLockedUntil(renewedOn);
|
||||||
|
|
||||||
|
// The new term is priced off the CBS base interest-rate master -- the rate card staff maintain on
|
||||||
|
// the Base Interest Rates screen, per FD type, with its own active version. That master is what
|
||||||
|
// the bank publishes and what the account-opening wizard already shows, so pricing a renewal off
|
||||||
|
// anything else would put the deposit on rates nobody chose.
|
||||||
|
if (this.chart != null && baseChartSlabs != null && !baseChartSlabs.isEmpty()) {
|
||||||
|
this.chart.refreshFromSlabSnapshots(baseChartFields, baseChartSlabs);
|
||||||
|
} else if (this.chart != null) {
|
||||||
|
// No active base version for this FD type: fall back to the product's own chart so the
|
||||||
|
// renewal still gets current product rates rather than the ones frozen at opening.
|
||||||
|
// activeChart, not applicableChart -- the latter treats a chart with no end date as valid only
|
||||||
|
// up to today's business date, so a renewal dated in the future would find nothing.
|
||||||
|
final InterestRateChart productChart = product instanceof FixedDepositProduct
|
||||||
|
? ((FixedDepositProduct) product).activeChart(renewedOn)
|
||||||
|
: null;
|
||||||
|
if (productChart != null) {
|
||||||
|
this.chart.refreshFrom(productChart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The senior-citizen premium is stored per account and takes priority over the chart in
|
||||||
|
// getEffectiveInterestRateAsFraction(), so leaving it alone would price every future term of a
|
||||||
|
// senior-citizen deposit at the rate captured when the account was opened. Only overwritten when
|
||||||
|
// the caller found a currently published rate -- otherwise the existing one stands, so a missing
|
||||||
|
// rate version can never silently drop the depositor's premium.
|
||||||
|
if (isSeniorCitizen() && currentSeniorCitizenRate != null && currentSeniorCitizenRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
setSeniorCitizenInterestRate(currentSeniorCitizenRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The account never left ACTIVE, but updateMaturityStatus() flipped it to MATURED just before
|
||||||
|
// this ran, so the new term has to put it back.
|
||||||
|
this.status = SavingsAccountStatusType.ACTIVE.getValue();
|
||||||
|
setClosedOnDate(null);
|
||||||
|
}
|
||||||
|
|
||||||
public FixedDepositAccount reInvest(BigDecimal depositAmount, ExternalId externalId) {
|
public FixedDepositAccount reInvest(BigDecimal depositAmount, ExternalId externalId) {
|
||||||
|
|
||||||
final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount);
|
final DepositAccountTermAndPreClosure newAccountTermAndPreClosure = this.accountTermAndPreClosure.copy(depositAmount);
|
||||||
final SavingsProduct product = this.product;
|
final SavingsProduct product = this.product;
|
||||||
final InterestRateChart productChart = product.applicableChart(getClosedOnDate());
|
// Effective-dated pick, same as renewInPlace: an open-ended chart stays valid past today's
|
||||||
|
// business date, and the most recently effective chart wins when more than one covers the date.
|
||||||
|
// Falls back to applicableChart so a product whose charts have no usable from-date still resolves
|
||||||
|
// as it did before.
|
||||||
|
InterestRateChart productChart = product instanceof FixedDepositProduct
|
||||||
|
? ((FixedDepositProduct) product).activeChart(getClosedOnDate())
|
||||||
|
: null;
|
||||||
|
if (productChart == null) {
|
||||||
|
productChart = product.applicableChart(getClosedOnDate());
|
||||||
|
}
|
||||||
final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart);
|
final DepositAccountInterestRateChart newChart = DepositAccountInterestRateChart.from(productChart);
|
||||||
|
|
||||||
final AccountType accountType = AccountType.fromInt(this.accountType);
|
final AccountType accountType = AccountType.fromInt(this.accountType);
|
||||||
final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType.fromInt(this.interestPostingPeriodType);
|
// Re-price every interest-mechanics setting off the CURRENT product, not this (old) account's
|
||||||
final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
|
// frozen-at-opening snapshot -- same reasoning as interestRate/newChart below. Without this, a
|
||||||
.fromInt(this.interestCompoundingPeriodType);
|
// renewed FD kept running on whatever compounding/posting/calculation-type/lock-in/tax setting
|
||||||
final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.fromInt(this.interestCalculationType);
|
// was in force when the ORIGINAL account was opened, silently ignoring every product edit made
|
||||||
final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
|
// since (e.g. Daily->Quarterly compounding, or a lock-in added later).
|
||||||
.fromInt(this.interestCalculationDaysInYearType);
|
final SavingsPostingInterestPeriodType postingPeriodType = product.interestPostingPeriodType();
|
||||||
|
final SavingsCompoundingInterestPeriodType compoundingPeriodType = product.interestCompoundingPeriodType();
|
||||||
|
final SavingsInterestCalculationType interestCalculationType = product.interestCalculationType();
|
||||||
|
final SavingsInterestCalculationDaysInYearType daysInYearType = product.interestCalculationDaysInYearType();
|
||||||
final BigDecimal minRequiredOpeningBalance = null;
|
final BigDecimal minRequiredOpeningBalance = null;
|
||||||
final BigDecimal interestRate = BigDecimal.ZERO;
|
// Falls back to the product's own flat rate -- same fallback DepositAccountAssembler uses
|
||||||
|
// for a brand-new account -- so a product with no chart slab covering this tenure/amount
|
||||||
|
// doesn't silently reinvest the customer at 0%. getEffectiveInterestRateAsFraction() still
|
||||||
|
// re-prices off newChart (the product's CURRENT chart, reflecting any edits made since the
|
||||||
|
// original account was opened) whenever a matching slab exists.
|
||||||
|
final BigDecimal interestRate = product.nominalAnnualInterestRate();
|
||||||
final Set<SavingsAccountCharge> savingsAccountCharges = null;
|
final Set<SavingsAccountCharge> savingsAccountCharges = null;
|
||||||
final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType);
|
final SavingsPeriodFrequencyType lockinPeriodFrequencyType = product.lockinPeriodFrequencyType();
|
||||||
final Integer lockinPeriodFrequency = this.lockinPeriodFrequency;
|
final Integer lockinPeriodFrequency = product.lockinPeriodFrequency();
|
||||||
final boolean withdrawalFeeApplicableForTransfer = false;
|
final boolean withdrawalFeeApplicableForTransfer = false;
|
||||||
final String accountNumber = null;
|
final String accountNumber = null;
|
||||||
final boolean withHoldTax = this.withHoldTax;
|
final boolean withHoldTax = product.withHoldTax();
|
||||||
final FixedDepositAccount reInvestedAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product,
|
final FixedDepositAccount reInvestedAccount = FixedDepositAccount.createNewApplicationForSubmittal(client, group, product,
|
||||||
savingsOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType,
|
savingsOfficer, accountNumber, externalId, accountType, getClosedOnDate(), closedBy, interestRate, compoundingPeriodType,
|
||||||
postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
|
postingPeriodType, interestCalculationType, daysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
|
||||||
@@ -842,6 +1037,30 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount);
|
newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount);
|
||||||
newChart.updateDepositAccountReference(reInvestedAccount);
|
newChart.updateDepositAccountReference(reInvestedAccount);
|
||||||
|
|
||||||
|
// Carry the senior-citizen premium forward -- without this, a senior-citizen depositor's
|
||||||
|
// reinvested FD silently drops back to the standard rate on the new term.
|
||||||
|
reInvestedAccount.setSeniorCitizen(this.isSeniorCitizen());
|
||||||
|
reInvestedAccount.setSeniorCitizenInterestRate(this.getSeniorCitizenInterestRate());
|
||||||
|
|
||||||
|
// Track which account this was reinvested from, so staff/UI can trace the two accounts to
|
||||||
|
// each other instead of the link disappearing the moment the old one closes.
|
||||||
|
newAccountTermAndPreClosure.setReinvestedFromSavingsId(this.getId());
|
||||||
|
|
||||||
|
// Carry the ORIGINAL account's number forward across every renewal, so the customer keeps
|
||||||
|
// seeing one recognisable FD number even though each term is its own account/ledger row.
|
||||||
|
final String rootFdAccountNumber = this.accountTermAndPreClosure.getFdAccountNumber() != null
|
||||||
|
? this.accountTermAndPreClosure.getFdAccountNumber()
|
||||||
|
: this.getAccountNumber();
|
||||||
|
newAccountTermAndPreClosure.setFdAccountNumber(rootFdAccountNumber);
|
||||||
|
|
||||||
|
// Number this term within the chain, so the UI can label it "Renewal 2 of FD ...048".
|
||||||
|
newAccountTermAndPreClosure.setRenewalCycleNo(this.accountTermAndPreClosure.getRenewalCycleNo() + 1);
|
||||||
|
|
||||||
|
// Without this the renewed deposit has no maturity instruction, so it would mature once more and
|
||||||
|
// then sit idle rather than rolling over again as the customer asked. Done here rather than in
|
||||||
|
// the shared copy(), which Recurring Deposit renewal also uses.
|
||||||
|
newAccountTermAndPreClosure.carryForwardStandingInstructionsFrom(this.accountTermAndPreClosure);
|
||||||
|
|
||||||
return reInvestedAccount;
|
return reInvestedAccount;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -883,7 +1102,9 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
@Override
|
@Override
|
||||||
public void loadLazyCollections() {
|
public void loadLazyCollections() {
|
||||||
super.loadLazyCollections();
|
super.loadLazyCollections();
|
||||||
this.chart.getId();
|
if (this.chart != null) {
|
||||||
|
this.chart.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getDepositAmount() {
|
public BigDecimal getDepositAmount() {
|
||||||
@@ -899,6 +1120,52 @@ public class FixedDepositAccount extends SavingsAccount {
|
|||||||
return SavingsAccountStatusType.MATURED.getValue().equals(this.status);
|
return SavingsAccountStatusType.MATURED.getValue().equals(this.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the parent class's protected lock-in check to callers outside this class hierarchy. Needed by the
|
||||||
|
* Payment action's domain-service method, which enforces this itself for its reinvest branches rather than going
|
||||||
|
* through {@link #prematureClosure}, since those branches keep the account open on the same term instead of
|
||||||
|
* closing it.
|
||||||
|
*/
|
||||||
|
public boolean isLockedOnDate(final LocalDate transactionDate) {
|
||||||
|
return isAccountLocked(transactionDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date guards {@link #close} and {@link #prematureClosure} apply, minus the maturity-date bound, for a Payment
|
||||||
|
* that renews the deposit instead of closing it and so never reaches either of those methods.
|
||||||
|
* <p>
|
||||||
|
* Dropping the maturity bound is the point: Payment is allowed on an already-matured deposit. The other three
|
||||||
|
* matter more here than they do on a closure, because a renewal turns this date into the new term's interest
|
||||||
|
* pivot -- backdate it behind interest that has already been posted and the engine regenerates those posting
|
||||||
|
* periods, reverses the settled postings and re-posts them, which is exactly what the pivot exists to prevent.
|
||||||
|
*/
|
||||||
|
public void validateRenewalDate(final LocalDate renewedOn) {
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||||
|
.resource(FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME + DepositsApiConstants.preMatureCloseAction);
|
||||||
|
|
||||||
|
if (DateUtils.isAfterBusinessDate(renewedOn)) {
|
||||||
|
baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(renewedOn)
|
||||||
|
.failWithCode("cannot.be.a.future.date");
|
||||||
|
}
|
||||||
|
if (DateUtils.isBefore(renewedOn, getActivationDate())) {
|
||||||
|
baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(renewedOn)
|
||||||
|
.failWithCode("must.be.after.activation.date");
|
||||||
|
}
|
||||||
|
final List<SavingsAccountTransaction> savingsAccountTransactions = retrieveListOfTransactions();
|
||||||
|
if (!savingsAccountTransactions.isEmpty()) {
|
||||||
|
final SavingsAccountTransaction lastTransaction = savingsAccountTransactions.get(savingsAccountTransactions.size() - 1);
|
||||||
|
if (lastTransaction.isAfter(renewedOn)) {
|
||||||
|
baseDataValidator.reset().parameter(SavingsApiConstants.closedOnDateParamName).value(renewedOn)
|
||||||
|
.failWithCode("must.be.after.last.transaction.date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setClosedOnDate(final LocalDate closedOnDate) {
|
public void setClosedOnDate(final LocalDate closedOnDate) {
|
||||||
this.closedOnDate = closedOnDate;
|
this.closedOnDate = closedOnDate;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user