Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c961f713 |
+1
-20
@@ -137,7 +137,7 @@ public class CommandSourceService {
|
||||
String permission = commandSource.getPermissionCode();
|
||||
boolean isMakerChecker = configurationDomainService.isMakerCheckerEnabledForTask(permission);
|
||||
if (isMakerChecker || result.isRollbackTransaction()) {
|
||||
if (isApprovedByChecker || isSuperUserBypassAllowed(commandSource, user)) {
|
||||
if (isApprovedByChecker || user.isCheckerSuperUser()) {
|
||||
commandSource.markAsChecked(user);
|
||||
} else {
|
||||
if (commandSource.isSanitized()) {
|
||||
@@ -151,25 +151,6 @@ public class CommandSourceService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isSuperUserBypassAllowed(CommandSource commandSource, AppUser user) {
|
||||
if (!user.isCheckerSuperUser()) {
|
||||
return false;
|
||||
}
|
||||
if ("GLACCOUNT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CBSBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CHARGE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "LOANPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "SAVINGSPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CURRENTPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "SHAREPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "FIXEDDEPOSITPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "USER".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "ROLE".equalsIgnoreCase(commandSource.getEntityName())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sanitizeJson(@NonNull CommandSource commandSource, Set<String> sanitizeKeys) {
|
||||
if (sanitizeKeys == null || sanitizeKeys.isEmpty()) {
|
||||
return;
|
||||
|
||||
+72
-8
@@ -664,6 +664,78 @@ public class CommandWrapperBuilder {
|
||||
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() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "OFFICETRANSACTION";
|
||||
@@ -2211,14 +2283,6 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createCbsBaseInterestRate() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "CBSBASEINTERESTRATE";
|
||||
this.entityId = null;
|
||||
this.href = "/fixeddepositproducts/baseinterestrates";
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder updateFixedDepositProduct(final Long productId) {
|
||||
this.actionName = "UPDATE";
|
||||
this.entityName = "FIXEDDEPOSITPRODUCT";
|
||||
|
||||
-2
@@ -26,8 +26,6 @@ public interface ConfigurationDomainService {
|
||||
|
||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||
|
||||
boolean isMakerCheckerEnabled();
|
||||
|
||||
/**
|
||||
* Global kill-switch for MFA-factor-backed login enforcement. When true, users with at least
|
||||
* one verified, non-disabled row in {@code m_user_mfa_factor} must complete a 2FA challenge
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -16,13 +16,13 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.exception;
|
||||
package org.apache.fineract.portfolio.dds.batch.exception;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public class UserPendingRequestNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
public class BatchRunNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public UserPendingRequestNotFoundException(final Long id) {
|
||||
super("error.msg.userpendingrequest.id.invalid", "User pending request with identifier " + id + " does not exist", id);
|
||||
public BatchRunNotFoundException(final Long id) {
|
||||
super("error.msg.ddsbatchrun.id.invalid", "DDS batch run with identifier " + id + " does not exist", id);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,569 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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);
|
||||
}
|
||||
}
|
||||
+10
-14
@@ -16,21 +16,17 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.domain;
|
||||
package org.apache.fineract.portfolio.dds.party.exception;
|
||||
|
||||
public enum UserPendingRequestType {
|
||||
CREATE_USER,
|
||||
UPDATE_USER;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
public static UserPendingRequestType fromString(final String type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
for (final UserPendingRequestType requestType : values()) {
|
||||
if (requestType.name().equalsIgnoreCase(type)) {
|
||||
return requestType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown UserPendingRequestType: " + type);
|
||||
/**
|
||||
* 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
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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);
|
||||
}
|
||||
}
|
||||
+10
-15
@@ -16,22 +16,17 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.domain;
|
||||
package org.apache.fineract.portfolio.dds.transaction.exception;
|
||||
|
||||
public enum UserPendingRequestStatus {
|
||||
PENDING,
|
||||
APPROVED,
|
||||
REJECTED;
|
||||
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
public static UserPendingRequestStatus fromString(final String status) {
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
for (final UserPendingRequestStatus requestStatus : values()) {
|
||||
if (requestStatus.name().equalsIgnoreCase(status)) {
|
||||
return requestStatus;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown UserPendingRequestStatus: " + status);
|
||||
/**
|
||||
* 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
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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);
|
||||
}
|
||||
}
|
||||
+1
@@ -275,6 +275,7 @@ public class SavingsApiConstants {
|
||||
// Additional custom fields
|
||||
public static final String balanceFrequencyParamName = "balanceFrequency";
|
||||
public static final String accountClosedZeroBalanceInDayClosingParamName = "accountClosedZeroBalanceInDayClosing";
|
||||
public static final String productCategoryParamName = "productCategory";
|
||||
|
||||
// Current Product — new UI fields
|
||||
public static final String balanceSwapOptionParamName = "balanceSwapOption";
|
||||
|
||||
+1
@@ -144,6 +144,7 @@ public final class SavingsProductData implements Serializable {
|
||||
@Getter @Setter private Boolean enableLockinPeriod;
|
||||
@Getter @Setter private String balanceFrequency;
|
||||
@Getter @Setter private Boolean accountClosedZeroBalanceInDayClosing;
|
||||
@Getter @Setter private String productCategory;
|
||||
|
||||
// New current product fields
|
||||
@Getter @Setter private Boolean balanceSwapOption;
|
||||
|
||||
-5
@@ -61,11 +61,6 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMakerCheckerEnabled() {
|
||||
return getGlobalConfigurationPropertyData(GlobalConfigurationConstants.MAKER_CHECKER).isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllowedLoanStatusesForExternalAssetTransfer() {
|
||||
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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);
|
||||
}
|
||||
+12
-8
@@ -16,27 +16,31 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.handler;
|
||||
package org.apache.fineract.organisation.agent.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateWritePlatformService;
|
||||
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 = "CBSBASEINTERESTRATE", action = "CREATE")
|
||||
@RequiredArgsConstructor
|
||||
public class CreateCbsBaseInterestRateCommandHandler implements NewCommandSourceHandler {
|
||||
@CommandType(entity = "AGENT", action = "CREATE")
|
||||
public class CreateAgentCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final FdBaseInterestRateWritePlatformService writePlatformService;
|
||||
private final AgentWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateAgentCommandHandler(final AgentWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.saveInterestRates(command);
|
||||
return this.writePlatformService.createAgent(command);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -16,15 +16,14 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.service;
|
||||
package org.apache.fineract.organisation.agent.service;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.service.Page;
|
||||
import org.apache.fineract.useradministration.data.UserPendingRequestData;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.organisation.agent.data.AgentData;
|
||||
|
||||
public interface UserPendingReadPlatformService {
|
||||
public interface AgentReadPlatformService {
|
||||
|
||||
Page<UserPendingRequestData> retrieveAllPendingRequests(String status, String username, String fromDate, String toDate,
|
||||
Integer offset, Integer limit);
|
||||
Collection<AgentData> retrieveAll();
|
||||
|
||||
UserPendingRequestData retrievePendingRequest(Long id);
|
||||
AgentData retrieveOne(Long agentId);
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.organisation.commissionslab.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.commissionslab.service.CommissionSlabWritePlatformService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@CommandType(entity = "COMMISSIONSLAB", action = "CREATE")
|
||||
public class CreateCommissionSlabsCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final CommissionSlabWritePlatformService writePlatformService;
|
||||
|
||||
@Autowired
|
||||
public CreateCommissionSlabsCommandHandler(final CommissionSlabWritePlatformService writePlatformService) {
|
||||
this.writePlatformService = writePlatformService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createCommissionSlabs(command);
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.organisation.commissionslab.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.organisation.commissionslab.data.CommissionSlabData;
|
||||
|
||||
public interface CommissionSlabReadPlatformService {
|
||||
|
||||
Collection<CommissionSlabData> retrieveAll();
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -16,12 +16,12 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
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 FdBaseInterestRateWritePlatformService {
|
||||
public interface CommissionSlabWritePlatformService {
|
||||
|
||||
CommandProcessingResult saveInterestRates(JsonCommand command);
|
||||
CommandProcessingResult createCommissionSlabs(JsonCommand command);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.organisation.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);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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);
|
||||
}
|
||||
}
|
||||
+2
-7
@@ -16,15 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.domain;
|
||||
package org.apache.fineract.portfolio.dds.batch.domain;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface UserPendingRequestRepository extends JpaRepository<UserPendingRequest, Long>, JpaSpecificationExecutor<UserPendingRequest> {
|
||||
public interface BatchRunRepository extends JpaRepository<BatchRun, Long> {
|
||||
|
||||
Optional<UserPendingRequest> findByPendingLockKey(String pendingLockKey);
|
||||
|
||||
boolean existsByPendingLockKey(String pendingLockKey);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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;
|
||||
}
|
||||
}
|
||||
+4
-8
@@ -16,18 +16,14 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.useradministration.service;
|
||||
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 UserPendingWritePlatformService {
|
||||
public interface BatchRunWritePlatformService {
|
||||
|
||||
CommandProcessingResult submitCreateUser(JsonCommand command);
|
||||
CommandProcessingResult generateBatchRun(JsonCommand command);
|
||||
|
||||
CommandProcessingResult submitUpdateUser(Long userId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult approvePendingRequest(Long id);
|
||||
|
||||
CommandProcessingResult rejectPendingRequest(Long id, JsonCommand command);
|
||||
CommandProcessingResult postBatchRun(Long batchRunId, JsonCommand command);
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.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
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.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,23 +57,14 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
||||
"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 boolean enabled;
|
||||
private final RestTemplate restTemplate;
|
||||
private final MobileLookupLogRepository logRepository;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public MobileLookupServiceImpl(@Value("${fineract.mobile-lookup.base-url}") String baseUrl,
|
||||
@Value("${fineract.mobile-lookup.enabled:true}") boolean enabled,
|
||||
MobileLookupLogRepository logRepository, JdbcTemplate jdbcTemplate) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.enabled = enabled;
|
||||
this.restTemplate = new RestTemplate();
|
||||
this.logRepository = logRepository;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
@@ -112,39 +103,6 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
||||
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;
|
||||
|
||||
LOG.info("Calling DIP/MNRL lookup API: {}", url);
|
||||
@@ -444,26 +402,6 @@ public class MobileLookupServiceImpl implements MobileLookupService {
|
||||
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) {
|
||||
JsonElement el = json.get(key);
|
||||
return (el != null && !el.isJsonNull()) ? el.getAsString() : null;
|
||||
|
||||
-69
@@ -18,11 +18,6 @@
|
||||
*/
|
||||
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.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@@ -31,7 +26,6 @@ import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.DefaultValue;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.HeaderParam;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
@@ -40,11 +34,9 @@ import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -58,10 +50,6 @@ import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamE
|
||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.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.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.infrastructure.security.service.SqlValidator;
|
||||
@@ -75,9 +63,6 @@ import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformS
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountEditWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
|
||||
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;
|
||||
|
||||
@Path("/v1/currentaccounts")
|
||||
@@ -99,10 +84,6 @@ public class CurrentAccountsApiResource {
|
||||
private final CurrentAccountEditWritePlatformService currentAccountEditWritePlatformService;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
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
|
||||
@Path("template")
|
||||
@@ -356,54 +337,4 @@ public class CurrentAccountsApiResource {
|
||||
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
-11
@@ -29,10 +29,6 @@ import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||
@@ -46,7 +42,6 @@ public class FixedDepositProductsBaseInterestRatesApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDate.class,
|
||||
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
|
||||
@@ -82,11 +77,11 @@ public class FixedDepositProductsBaseInterestRatesApiResource {
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Save new FD base interest rate version", description = "Creates a new FD CBS base chart version for the given fdType and deactivates the previous active version.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public CommandProcessingResult saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.createCbsBaseInterestRate()
|
||||
.withJson(apiRequestBodyAsJson)
|
||||
.build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
public String saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||
final FdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, FdInterestRateSaveRequest.class);
|
||||
final Long createdById = this.context.authenticatedUser().getId();
|
||||
final FdInterestRateEffectiveData saved = this.fdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById);
|
||||
return gson.toJson(saved);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -435,10 +435,7 @@ public class FixedDepositAccount extends SavingsAccount {
|
||||
|
||||
@Override
|
||||
public Money activateWithBalance() {
|
||||
// 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);
|
||||
return Money.of(this.currency, this.accountTermAndPreClosure.depositAmount());
|
||||
}
|
||||
|
||||
public void close(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges) {
|
||||
|
||||
+1
-4
@@ -552,10 +552,7 @@ public class RecurringDepositAccount extends SavingsAccount {
|
||||
|
||||
@Override
|
||||
public Money activateWithBalance() {
|
||||
// Activation never deposits money -- funding happens strictly through a separate deposit
|
||||
// transaction (posted by a clerk) after the account is already active, regardless of any
|
||||
// minimum required opening balance the product has configured.
|
||||
return Money.zero(this.currency);
|
||||
return Money.of(this.currency, this.minRequiredOpeningBalance);
|
||||
}
|
||||
|
||||
protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals,
|
||||
|
||||
+13
-4
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountStatusParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianClientIdParamName;
|
||||
@@ -454,10 +455,18 @@ public class SavingsAccountAssembler {
|
||||
account.setLinkedAccountType(this.fromApiJsonHelper.extractStringNamed(linkedAccountTypeParamName, element));
|
||||
account.setLinkedAccountNo(this.fromApiJsonHelper.extractStringNamed(linkedAccountNoParamName, element));
|
||||
|
||||
// Every digital facility starts disabled at account opening, regardless of anything the
|
||||
// request sends or the product defaults to -- they're switched on deliberately, one at a
|
||||
// time, later, through the account's own Edit Nominee & Features screen.
|
||||
account.setFacilityConfig(new SavingsAccountFacilityConfig(account));
|
||||
// Account Facility Config
|
||||
if (element.getAsJsonObject().has(accountFacilityConfigParamName)) {
|
||||
final JsonElement configEl = element.getAsJsonObject().get(accountFacilityConfigParamName);
|
||||
if (configEl != null && !configEl.isJsonNull() && configEl.isJsonObject()) {
|
||||
SavingsAccountFacilityConfig facilityConfig = account.getFacilityConfig();
|
||||
if (facilityConfig == null) {
|
||||
facilityConfig = new SavingsAccountFacilityConfig(account);
|
||||
account.setFacilityConfig(facilityConfig);
|
||||
}
|
||||
facilityConfig.updateFrom(configEl.getAsJsonObject());
|
||||
}
|
||||
}
|
||||
|
||||
// Joint holders
|
||||
if (element.getAsJsonObject().has(jointHoldersParamName)) {
|
||||
|
||||
+11
-3
@@ -133,10 +133,18 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
||||
"Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId);
|
||||
}
|
||||
|
||||
// Minimum required balance is not enforced -- a withdrawal must never be blocked for
|
||||
// taking the account below its configured minimum, only for exceeding the available
|
||||
// balance (checked above).
|
||||
// Minimum required balance validation
|
||||
final BigDecimal newBalance = currentBalance.subtract(amount);
|
||||
if (account.isEnforceMinRequiredBalance() && account.getMinRequiredBalance() != null
|
||||
&& account.getMinRequiredBalance().compareTo(BigDecimal.ZERO) > 0) {
|
||||
if (newBalance.compareTo(account.getMinRequiredBalance()) < 0) {
|
||||
throw new GeneralPlatformDomainRuleException(
|
||||
"error.msg.current.account.min.required.balance.breached",
|
||||
"Withdrawal of " + amount + " would breach the minimum required balance of "
|
||||
+ account.getMinRequiredBalance() + ". Balance after withdrawal would be " + newBalance,
|
||||
accountId);
|
||||
}
|
||||
}
|
||||
|
||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
||||
final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo);
|
||||
|
||||
+151
-11
@@ -227,10 +227,12 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
clientNomineeWritePlatformService.saveNomineeIfPresent(clientId, accountNo, "CB", json.get("nominee"));
|
||||
}
|
||||
|
||||
// Every digital facility starts disabled at account opening, regardless of anything the
|
||||
// request sends or the product defaults to -- they're switched on deliberately, one at a
|
||||
// time, later, through the account's own Edit Nominee & Features screen.
|
||||
account.setFacilityConfig(CurrentAccountFacilityConfig.createFor(account));
|
||||
// Facility config — UI sends "accountFacilityConfig", internal uses "facilityConfig"
|
||||
JsonObject fcJson = json.getAsJsonObject("accountFacilityConfig");
|
||||
if (fcJson == null) fcJson = json.getAsJsonObject("facilityConfig");
|
||||
if (fcJson != null) {
|
||||
account.setFacilityConfig(assembleFacilityConfig(account, fcJson));
|
||||
}
|
||||
|
||||
// Joint holders
|
||||
if (json.has("jointHolders") && json.get("jointHolders").isJsonArray()) {
|
||||
@@ -292,16 +294,61 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
final JsonObject json = command.parsedJson().getAsJsonObject();
|
||||
LocalDate activatedOnDate = parseDate(getString(json, "activatedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
|
||||
if (activatedOnDate == null) activatedOnDate = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||
// Validate minimum opening balance before activation
|
||||
java.math.BigDecimal openingBalanceValue = java.math.BigDecimal.ZERO;
|
||||
if (json.has("openingBalance") && !json.get("openingBalance").isJsonNull()) {
|
||||
try { openingBalanceValue = json.get("openingBalance").getAsBigDecimal(); } catch (Exception ignored) {
|
||||
// intentionally ignored
|
||||
}
|
||||
}
|
||||
final java.math.BigDecimal minRequired = account.getMinRequiredOpeningBalance();
|
||||
if (minRequired != null && minRequired.compareTo(java.math.BigDecimal.ZERO) > 0
|
||||
&& openingBalanceValue.compareTo(minRequired) < 0) {
|
||||
throw new org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException(
|
||||
java.util.List.of(org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterError(
|
||||
"openingBalance",
|
||||
"Opening balance (" + openingBalanceValue + ") is less than minimum required opening balance (" + minRequired + ")",
|
||||
"validation.msg.currentaccount.openingBalance.less.than.min.required",
|
||||
openingBalanceValue, minRequired)));
|
||||
}
|
||||
|
||||
account.activate(activatedOnDate, user);
|
||||
applyFacilityChargesOnActivation(account);
|
||||
|
||||
// Activation posts no transaction of its own -- no opening-balance deposit, no
|
||||
// digital-facility charges applied or collected, regardless of what the account's facility
|
||||
// config or product defaults say. Funding and any facility charges happen strictly
|
||||
// afterwards, through separate transactions a clerk processes once the account is already
|
||||
// active.
|
||||
java.math.BigDecimal currentBalance = java.math.BigDecimal.ZERO;
|
||||
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction> depositTxns = new java.util.ArrayList<>();
|
||||
final java.util.List<ChargeGlEntry> chargeEntries = new java.util.ArrayList<>();
|
||||
|
||||
// Opening balance deposit transaction
|
||||
if (openingBalanceValue.compareTo(java.math.BigDecimal.ZERO) > 0) {
|
||||
try {
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction deposit =
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction
|
||||
.deposit(account, account.getOffice(), openingBalanceValue, activatedOnDate, "OPENING-BALANCE");
|
||||
deposit.setRunningBalanceDerived(openingBalanceValue);
|
||||
account.setTotalDepositsDerived(openingBalanceValue);
|
||||
currentAccountTransactionRepository.saveAndFlush(deposit);
|
||||
currentBalance = openingBalanceValue;
|
||||
depositTxns.add(deposit);
|
||||
log.info("Opening balance deposit {} created for account id={}", openingBalanceValue, accountId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not process openingBalance for account id={}: {}", accountId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-deduct activation charges (chargeTimeEnum=3) from opening balance
|
||||
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge> activationCharges =
|
||||
account.getCharges().stream()
|
||||
.filter(c -> c.getChargeTimeEnum() != null && c.getChargeTimeEnum() == 3 && !c.isPaid())
|
||||
.toList();
|
||||
currentBalance = collectChargesAndPost(account, activationCharges, currentBalance, activatedOnDate, chargeEntries);
|
||||
|
||||
account.setAccountBalanceDerived(currentBalance);
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
|
||||
// Post GL journal entries — deposits in batch, each charge individually
|
||||
postGlEntries(account, depositTxns, chargeEntries);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
|
||||
}
|
||||
|
||||
@@ -590,6 +637,78 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
}
|
||||
}
|
||||
|
||||
private void applyFacilityChargesOnActivation(final CurrentAccount account) {
|
||||
// Use account-level facility config — reflects what was selected at account opening time
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig config =
|
||||
account.getFacilityConfig();
|
||||
|
||||
log.info("[FacilityCharge] Activating account id={}, facilityConfig={}",
|
||||
account.getId(), config != null ? "FOUND" : "NULL");
|
||||
|
||||
if (config == null) {
|
||||
// Fallback: use product-level facility config
|
||||
log.info("[FacilityCharge] No account-level config, falling back to product id={}", account.getProductId());
|
||||
config = currentProductRepository.findById(account.getProductId())
|
||||
.map(p -> {
|
||||
final var pc = p.getFacilityConfig();
|
||||
if (pc == null) {
|
||||
log.info("[FacilityCharge] Product has no facility config either");
|
||||
return null;
|
||||
}
|
||||
// Map product config to account config fields inline
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig ac =
|
||||
new org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig();
|
||||
ac.setAtmFacility(Boolean.TRUE.equals(pc.getAtmFacility()));
|
||||
ac.setAtmWithdrawalFacility(Boolean.TRUE.equals(pc.getAtmWithdrawalFacility()));
|
||||
ac.setUpiFacility(Boolean.TRUE.equals(pc.getUpiFacility()));
|
||||
ac.setPosTransactionsFacility(Boolean.TRUE.equals(pc.getPosTransactionsFacility()));
|
||||
ac.setChequeBook(Boolean.TRUE.equals(pc.getChequeBook()));
|
||||
ac.setImpsFacility(Boolean.TRUE.equals(pc.getImpsFacility()));
|
||||
ac.setSmsAlerts(Boolean.TRUE.equals(pc.getSmsAlerts()));
|
||||
ac.setBbpsFacility(Boolean.TRUE.equals(pc.getBbpsFacility()));
|
||||
ac.setInsuranceCover(Boolean.TRUE.equals(pc.getInsuranceCover()));
|
||||
ac.setBepgFacility(Boolean.TRUE.equals(pc.getBepgFacility()));
|
||||
ac.setNeftFacility(Boolean.TRUE.equals(pc.getNeftFacility()));
|
||||
ac.setRtgsFacility(Boolean.TRUE.equals(pc.getRtgsFacility()));
|
||||
ac.setInterBankTotalCapFacility(Boolean.TRUE.equals(pc.getInterBankTotalCapFacility()));
|
||||
ac.setWithdrawalFacility(Boolean.TRUE.equals(pc.getWithdrawalFacility()));
|
||||
ac.setDepositFacilityKyc(Boolean.TRUE.equals(pc.getDepositFacilityKyc()));
|
||||
ac.setVirtualDebitCard(Boolean.TRUE.equals(pc.getVirtualDebitCard()));
|
||||
return ac;
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
log.warn("[FacilityCharge] No facility config found for account id={} — no charges applied", account.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig fc = config;
|
||||
log.info("[FacilityCharge] Config found — ATM={} UPI={} NEFT={} RTGS={} CHEQUE={} SMS={} IMPs={} POS={} BBPS={} INS={} BEPG={} IBCAP={} WD={} KYC={} VCARD={} ATM_WD={}",
|
||||
fc.isAtmFacility(), fc.isUpiFacility(), fc.isNeftFacility(), fc.isRtgsFacility(),
|
||||
fc.isChequeBook(), fc.isSmsAlerts(), fc.isImpsFacility(), fc.isPosTransactionsFacility(),
|
||||
fc.isBbpsFacility(), fc.isInsuranceCover(), fc.isBepgFacility(), fc.isInterBankTotalCapFacility(),
|
||||
fc.isWithdrawalFacility(), fc.isDepositFacilityKyc(), fc.isVirtualDebitCard(), fc.isAtmWithdrawalFacility());
|
||||
applyMapped(account, fc.isAtmFacility(), "ATM_FACILITY");
|
||||
applyMapped(account, fc.isAtmWithdrawalFacility(), "ATM_WITHDRAWAL");
|
||||
applyMapped(account, fc.isUpiFacility(), "UPI_FACILITY");
|
||||
applyMapped(account, fc.isPosTransactionsFacility(), "POS_TRANSACTIONS");
|
||||
// Cheque book: no longer charged at activation — the per-leaf issuance charge is now deducted
|
||||
// when a cheque book is actually issued (ChequeBookIssuanceChargeService), not just for having
|
||||
// the facility enabled.
|
||||
applyMapped(account, fc.isImpsFacility(), "IMPS_FACILITY");
|
||||
applyMapped(account, fc.isSmsAlerts(), "SMS_ALERTS");
|
||||
applyMapped(account, fc.isBbpsFacility(), "BBPS_FACILITY");
|
||||
applyMapped(account, fc.isInsuranceCover(), "INSURANCE_COVER");
|
||||
applyMapped(account, fc.isBepgFacility(), "BEPG_FACILITY");
|
||||
applyMapped(account, fc.isNeftFacility(), "NEFT_FACILITY");
|
||||
applyMapped(account, fc.isRtgsFacility(), "RTGS_FACILITY");
|
||||
applyMapped(account, fc.isInterBankTotalCapFacility(),"INTER_BANK_TOTAL_CAP");
|
||||
applyMapped(account, fc.isWithdrawalFacility(), "WITHDRAWAL_FACILITY");
|
||||
applyMapped(account, fc.isDepositFacilityKyc(), "DEPOSIT_FACILITY_KYC");
|
||||
applyMapped(account, fc.isVirtualDebitCard(), "VIRTUAL_DEBIT_CARD");
|
||||
}
|
||||
|
||||
// Wrapper to carry a charge transaction together with its charge entity for GL posting
|
||||
private record ChargeGlEntry(
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn,
|
||||
@@ -598,8 +717,9 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
/**
|
||||
* Marks each of the given (unpaid) charges as paid, deducts its amount from {@code runningBalance}, creates a
|
||||
* {@code CurrentAccountTransaction.chargeDeduction(...)} row for it, and records a {@link ChargeGlEntry} for GL
|
||||
* posting. Used by {@link #applyAndCollectFacilityCharges(Long, java.util.Set)} (edit-approval-time charges) --
|
||||
* activation itself no longer collects any charges. Returns the resulting balance.
|
||||
* posting. Shared by {@link #activate(Long, JsonCommand)} (activation-time charges) and
|
||||
* {@link #applyAndCollectFacilityCharges(Long, java.util.Set)} (edit-approval-time charges) so the
|
||||
* charge-collection logic isn't duplicated. Returns the resulting balance.
|
||||
*/
|
||||
private java.math.BigDecimal collectChargesAndPost(final CurrentAccount account,
|
||||
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge> charges,
|
||||
@@ -755,4 +875,24 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
}
|
||||
}
|
||||
|
||||
private void applyMapped(final CurrentAccount account, final boolean enabled, final String facilityType) {
|
||||
if (!enabled) {
|
||||
log.debug("[FacilityCharge] {} disabled — skip", facilityType);
|
||||
return;
|
||||
}
|
||||
final var mappingOpt = facilityChargeMappingRepository.findByFacilityType(facilityType);
|
||||
if (mappingOpt.isEmpty()) {
|
||||
log.warn("[FacilityCharge] {} enabled but NO mapping found in m_facility_charge_mapping", facilityType);
|
||||
return;
|
||||
}
|
||||
final org.apache.fineract.portfolio.charge.domain.Charge charge = mappingOpt.get().getCharge();
|
||||
if (charge == null) {
|
||||
log.warn("[FacilityCharge] {} mapping exists but charge_id is NULL", facilityType);
|
||||
return;
|
||||
}
|
||||
log.info("[FacilityCharge] Applying charge '{}' (id={}) for facility {}", charge.getName(), charge.getId(), facilityType);
|
||||
account.getCharges().add(
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge
|
||||
.createFromFacility(account, charge));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-40
@@ -172,14 +172,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
|
||||
if (portfolioAccountData == null) {
|
||||
final PaymentDetail paymentDetail = null;
|
||||
final SavingsAccountTransaction depositTransaction = this.depositAccountDomainService.handleFDDeposit(account, fmt,
|
||||
account.getActivationDate(), amountForDeposit.getAmount(), paymentDetail);
|
||||
// handleFDDeposit() already posts this transaction's own journal entries internally --
|
||||
// mark it as "existing" so the postJournalEntries(...) call below (which covers other
|
||||
// activation-time changes, e.g. an activation charge payment) does not post it again.
|
||||
if (depositTransaction != null && depositTransaction.getId() != null) {
|
||||
existingTransactionIds.add(depositTransaction.getId());
|
||||
}
|
||||
this.depositAccountDomainService.handleFDDeposit(account, fmt, account.getActivationDate(),
|
||||
amountForDeposit.getAmount(), paymentDetail);
|
||||
} else {
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
boolean isRegularTransaction = false;
|
||||
@@ -208,17 +202,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
}
|
||||
|
||||
final boolean isPreMatureClosure = false;
|
||||
if (amountForDeposit.isGreaterThanZero()) {
|
||||
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth);
|
||||
} else {
|
||||
// No deposit has actually landed on the account yet -- project maturity using the
|
||||
// configured deposit amount (the same approach used for the pre-activation preview and
|
||||
// for reinvested FDs) instead of the account's real, still-empty transaction history,
|
||||
// which would otherwise price in zero interest.
|
||||
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure,
|
||||
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth);
|
||||
}
|
||||
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth);
|
||||
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
|
||||
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
|
||||
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
|
||||
@@ -290,14 +275,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
final PortfolioAccountData portfolioAccountData = this.accountAssociationsReadPlatformService
|
||||
.retriveSavingsLinkedAssociation(savingsId);
|
||||
if (portfolioAccountData == null) {
|
||||
final SavingsAccountTransaction depositTransaction = this.depositAccountDomainService.handleRDDeposit(account, fmt,
|
||||
account.getActivationDate(), amountForDeposit.getAmount(), null, isRegularTransaction);
|
||||
// handleRDDeposit() already posts this transaction's own journal entries internally --
|
||||
// mark it as "existing" so the postJournalEntries(...) call below (which covers other
|
||||
// activation-time changes) does not post it again.
|
||||
if (depositTransaction != null && depositTransaction.getId() != null) {
|
||||
existingTransactionIds.add(depositTransaction.getId());
|
||||
}
|
||||
this.depositAccountDomainService.handleRDDeposit(account, fmt, account.getActivationDate(),
|
||||
amountForDeposit.getAmount(), null, isRegularTransaction);
|
||||
} else {
|
||||
final boolean isExceptionForBalanceCheck = false;
|
||||
final SavingsAccount fromSavingsAccount = null;
|
||||
@@ -389,19 +368,6 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
final SavingsAccountTransaction deposit = this.depositAccountDomainService.handleFDDeposit(account, fmt, transactionDate,
|
||||
transactionAmount, paymentDetail);
|
||||
|
||||
// The account may have been activated with no funds yet (opening deposit is now a separate,
|
||||
// later step) -- in that case its maturity amount was only ever projected off the configured
|
||||
// deposit amount, with zero real transactions. Now that money has actually landed, recompute
|
||||
// maturity date/amount off the account's real transaction history so it reflects reality.
|
||||
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
|
||||
.isSavingsInterestPostingAtCurrentPeriodEnd();
|
||||
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
final boolean isPreMatureClosure = false;
|
||||
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth);
|
||||
this.savingAccountRepositoryWrapper.saveAndFlush(account);
|
||||
|
||||
this.denominationBreakdownWriter.save(command, deposit.getId(), account.getId(), account.getAccountNumber(),
|
||||
account.clientId(), account.getClient() != null ? account.getClient().getCifNumber() : null, transactionDate,
|
||||
"DEPOSIT");
|
||||
|
||||
+5
@@ -324,6 +324,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.product_category as productCategory, ");
|
||||
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
|
||||
sqlBuilder.append("sp.lien_marked as lienMarked, sp.maturity_instructions as maturityInstructions, ");
|
||||
@@ -412,6 +413,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
result.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
|
||||
result.setBalanceFrequency(rs.getString("balanceFrequency"));
|
||||
result.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
|
||||
result.setProductCategory(rs.getString("productCategory"));
|
||||
result.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
|
||||
result.setLoanFacility(rs.getBoolean("loanFacility"));
|
||||
result.setPrematureWithdrawalOption(rs.getBoolean("prematureWithdrawalOption"));
|
||||
@@ -463,6 +465,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.product_category as productCategory, ");
|
||||
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
|
||||
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
|
||||
@@ -539,6 +542,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
final boolean enableLockinPeriod = rs.getBoolean("enableLockinPeriod");
|
||||
final String balanceFrequency = rs.getString("balanceFrequency");
|
||||
final boolean accountClosedZeroBalanceInDayClosing = rs.getBoolean("accountClosedZeroBalanceInDayClosing");
|
||||
final String productCategory = rs.getString("productCategory");
|
||||
final boolean balanceSwapOption = rs.getBoolean("balanceSwapOption");
|
||||
final boolean msme = rs.getBoolean("msme");
|
||||
final boolean autoDebitFacility = rs.getBoolean("autoDebitFacility");
|
||||
@@ -581,6 +585,7 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
result.setEnableLockinPeriod(enableLockinPeriod);
|
||||
result.setBalanceFrequency(balanceFrequency);
|
||||
result.setAccountClosedZeroBalanceInDayClosing(accountClosedZeroBalanceInDayClosing);
|
||||
result.setProductCategory(productCategory);
|
||||
result.setBalanceSwapOption(balanceSwapOption);
|
||||
result.setMsme(msme);
|
||||
result.setAutoDebitFacility(autoDebitFacility);
|
||||
|
||||
+1
-128
@@ -11,16 +11,10 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData;
|
||||
@@ -57,9 +51,7 @@ public class FdBaseInterestRateReadPlatformServiceImpl implements FdBaseInterest
|
||||
@Override
|
||||
@Transactional
|
||||
public FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById) {
|
||||
validateRequest(request);
|
||||
|
||||
final String fdType = request.getFdType().toUpperCase();
|
||||
final String fdType = request.getFdType();
|
||||
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||
|
||||
final List<FdBaseInterestRateEffective> activeVersions = effectiveRepository
|
||||
@@ -93,125 +85,6 @@ public class FdBaseInterestRateReadPlatformServiceImpl implements FdBaseInterest
|
||||
return toData(newVersion);
|
||||
}
|
||||
|
||||
private void validateRequest(final FdInterestRateSaveRequest request) {
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("FD_BASE_INTEREST_RATE_VERSION");
|
||||
|
||||
if (request == null) {
|
||||
validator.failWithCodeNoParameterAddedToErrorCode("request.cannot.be.null");
|
||||
throwExceptionIfValidationWarningsExist(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
final String fdType = request.getFdType();
|
||||
if (fdType == null || fdType.isBlank()) {
|
||||
validator.parameter("fdType").failWithCode("cannot.be.blank");
|
||||
} else if (!List.of("SINGLE", "DOUBLE", "SPECIAL").contains(fdType.toUpperCase())) {
|
||||
validator.parameter("fdType").value(fdType).failWithCode("invalid.fd.type");
|
||||
}
|
||||
|
||||
if (request.getSlabs() == null || request.getSlabs().isEmpty()) {
|
||||
validator.parameter("slabs").failWithCode("cannot.be.blank");
|
||||
}
|
||||
|
||||
if (request.getVersionLabel() != null && request.getVersionLabel().length() > 100) {
|
||||
validator.parameter("versionLabel").value(request.getVersionLabel()).failWithCode("exceeds.max.length");
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(errors);
|
||||
validateSlabs(request.getSlabs());
|
||||
}
|
||||
|
||||
private void validateSlabs(final List<FdInterestRateSaveRequest.SlabRequest> slabs) {
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource("FD_BASE_INTEREST_RATE_VERSION");
|
||||
|
||||
if (slabs == null || slabs.isEmpty()) {
|
||||
validator.parameter("slabs").failWithCode("cannot.be.blank");
|
||||
throwExceptionIfValidationWarningsExist(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<FdInterestRateSaveRequest.SlabRequest> sorted = new ArrayList<>(slabs);
|
||||
sorted.sort(Comparator.comparing((FdInterestRateSaveRequest.SlabRequest slab) -> slab.getSrNo() == null ? Integer.MAX_VALUE : slab.getSrNo())
|
||||
.thenComparing(slab -> slab.getFromPeriod() == null ? Integer.MAX_VALUE : slab.getFromPeriod()));
|
||||
|
||||
final Integer expectedPeriodType = sorted.get(0).getPeriodTypeEnum();
|
||||
Integer expectedFrom = 1;
|
||||
|
||||
for (int i = 0; i < sorted.size(); i++) {
|
||||
final FdInterestRateSaveRequest.SlabRequest slab = sorted.get(i);
|
||||
final int srNo = slab.getSrNo() == null ? i + 1 : slab.getSrNo();
|
||||
final String slabPrefix = "slabs[" + srNo + "].";
|
||||
|
||||
if (slab.getPeriodTypeEnum() == null) {
|
||||
validator.parameter(slabPrefix + "periodTypeEnum").failWithCode("cannot.be.blank");
|
||||
} else if (!slab.getPeriodTypeEnum().equals(expectedPeriodType)) {
|
||||
validator.parameter(slabPrefix + "periodTypeEnum").value(slab.getPeriodTypeEnum())
|
||||
.failWithCode("must.be.consistent.across.all.slabs");
|
||||
}
|
||||
|
||||
final Integer fromPeriod = slab.getFromPeriod();
|
||||
if (fromPeriod == null) {
|
||||
validator.parameter(slabPrefix + "fromPeriod").failWithCode("cannot.be.blank");
|
||||
} else if (fromPeriod < 0) {
|
||||
validator.parameter(slabPrefix + "fromPeriod").value(fromPeriod).failWithCode("must.be.non.negative");
|
||||
} else if (!fromPeriod.equals(expectedFrom)) {
|
||||
if (i == 0) {
|
||||
validator.parameter(slabPrefix + "fromPeriod").value(fromPeriod).failWithCode("first.slab.must.start.at.one");
|
||||
} else if (fromPeriod < expectedFrom) {
|
||||
validator.parameter(slabPrefix + "fromPeriod").value(fromPeriod).failWithCode("must.not.overlap.previous.range");
|
||||
} else {
|
||||
validator.parameter(slabPrefix + "fromPeriod").value(fromPeriod).failWithCode("must.be.continuous.with.previous.range");
|
||||
}
|
||||
}
|
||||
|
||||
final Integer toPeriod = slab.getToPeriod();
|
||||
if (i < sorted.size() - 1) {
|
||||
if (toPeriod == null) {
|
||||
validator.parameter(slabPrefix + "toPeriod").failWithCode("cannot.be.blank.for.intermediate.slab");
|
||||
} else {
|
||||
if (toPeriod < 0) {
|
||||
validator.parameter(slabPrefix + "toPeriod").value(toPeriod).failWithCode("must.be.non.negative");
|
||||
}
|
||||
if (fromPeriod != null && toPeriod < fromPeriod) {
|
||||
validator.parameter(slabPrefix + "toPeriod").value(toPeriod).failWithCode("cannot.be.less.than.fromPeriod");
|
||||
}
|
||||
expectedFrom = toPeriod + 1;
|
||||
}
|
||||
} else {
|
||||
if (toPeriod != null) {
|
||||
if (toPeriod < 0) {
|
||||
validator.parameter(slabPrefix + "toPeriod").value(toPeriod).failWithCode("must.be.non.negative");
|
||||
}
|
||||
if (fromPeriod != null && toPeriod < fromPeriod) {
|
||||
validator.parameter(slabPrefix + "toPeriod").value(toPeriod).failWithCode("cannot.be.less.than.fromPeriod");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal annualRate = slab.getAnnualInterestRate();
|
||||
if (annualRate == null) {
|
||||
validator.parameter(slabPrefix + "annualInterestRate").failWithCode("cannot.be.blank");
|
||||
} else if (annualRate.compareTo(BigDecimal.ZERO) < 0) {
|
||||
validator.parameter(slabPrefix + "annualInterestRate").value(annualRate).failWithCode("must.be.non.negative");
|
||||
}
|
||||
|
||||
final BigDecimal seniorRate = slab.getSeniorCitizenRate();
|
||||
if (seniorRate != null && seniorRate.compareTo(BigDecimal.ZERO) < 0) {
|
||||
validator.parameter(slabPrefix + "seniorCitizenRate").value(seniorRate).failWithCode("must.be.non.negative");
|
||||
}
|
||||
}
|
||||
|
||||
throwExceptionIfValidationWarningsExist(errors);
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private FdInterestRateEffectiveData toData(FdBaseInterestRateEffective e) {
|
||||
List<FdInterestRateSlabData> slabs = slabRepository.findByEffectiveIdOrderBySrNo(e.getId()).stream()
|
||||
.map(s -> FdInterestRateSlabData.builder()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user