allow adjusted transactions to be zeroed, ensure inArrearsTolerance is nullable and does not need to be provided.
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
|||||||
|
alter table m_loan_transaction modify column principal_portion_derived decimal(19,6);
|
||||||
|
|
||||||
|
alter table m_loan_transaction modify column interest_portion_derived decimal(19,6);
|
||||||
|
|
||||||
|
alter table m_loan_transaction modify column charges_portion_derived decimal(19,6);
|
||||||
|
|
||||||
|
|
||||||
|
alter table m_product_loan modify column arrearstolerance_amount decimal(19,6);
|
||||||
+8
-2
@@ -35,7 +35,9 @@ public class LoanProductCommand {
|
|||||||
|
|
||||||
private final Set<String> modifiedParameters;
|
private final Set<String> modifiedParameters;
|
||||||
|
|
||||||
public LoanProductCommand(final Set<String> modifiedParameters, final Long id, final String name, final String description,
|
public LoanProductCommand(
|
||||||
|
final Set<String> modifiedParameters,
|
||||||
|
final Long id, final String name, final String description,
|
||||||
final Long fundId, final Long transactionProcessingStrategyId,
|
final Long fundId, final Long transactionProcessingStrategyId,
|
||||||
final String currencyCode, final Integer digitsAfterDecimal,
|
final String currencyCode, final Integer digitsAfterDecimal,
|
||||||
final BigDecimal principal, final BigDecimal inArrearsTolerance, final Integer numberOfRepayments,
|
final BigDecimal principal, final BigDecimal inArrearsTolerance, final Integer numberOfRepayments,
|
||||||
@@ -51,7 +53,11 @@ public class LoanProductCommand {
|
|||||||
this.currencyCode = currencyCode;
|
this.currencyCode = currencyCode;
|
||||||
this.digitsAfterDecimal = digitsAfterDecimal;
|
this.digitsAfterDecimal = digitsAfterDecimal;
|
||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
this.inArrearsTolerance = inArrearsTolerance;
|
if (inArrearsTolerance != null && BigDecimal.ZERO.compareTo(inArrearsTolerance) == 0) {
|
||||||
|
this.inArrearsTolerance = null;
|
||||||
|
} else {
|
||||||
|
this.inArrearsTolerance = inArrearsTolerance;
|
||||||
|
}
|
||||||
this.numberOfRepayments = numberOfRepayments;
|
this.numberOfRepayments = numberOfRepayments;
|
||||||
this.repaymentEvery = repaymentEvery;
|
this.repaymentEvery = repaymentEvery;
|
||||||
this.interestRatePerPeriod = interestRatePerPeriod;
|
this.interestRatePerPeriod = interestRatePerPeriod;
|
||||||
|
|||||||
@@ -616,7 +616,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
|
|
||||||
boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions);
|
boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction, this.loanTransactions);
|
||||||
|
|
||||||
this.loanTransactions.add(loanTransaction);
|
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||||
|
this.loanTransactions.add(loanTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
if (loanTransaction.isNotRepayment() && loanTransaction.isNotInterestWaiver()) {
|
if (loanTransaction.isNotRepayment() && loanTransaction.isNotInterestWaiver()) {
|
||||||
final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
|
final String errorMessage = "A transaction of type repayment or waiver was expected but not received.";
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.mifosng.platform.api.commands.LoanProductCommand;
|
import org.mifosng.platform.api.commands.LoanProductCommand;
|
||||||
import org.mifosng.platform.charge.domain.Charge;
|
import org.mifosng.platform.charge.domain.Charge;
|
||||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||||
import org.mifosng.platform.currency.domain.Money;
|
|
||||||
import org.mifosng.platform.fund.domain.Fund;
|
import org.mifosng.platform.fund.domain.Fund;
|
||||||
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
|
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
|
||||||
import org.mifosng.platform.user.domain.AppUser;
|
import org.mifosng.platform.user.domain.AppUser;
|
||||||
@@ -33,17 +32,21 @@ import org.mifosng.platform.user.domain.AppUser;
|
|||||||
@Table(name = "m_product_loan")
|
@Table(name = "m_product_loan")
|
||||||
public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
|
public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "fund_id", nullable = true)
|
@JoinColumn(name = "fund_id", nullable = true)
|
||||||
private Fund fund;
|
private Fund fund;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "loan_transaction_strategy_id", nullable = true)
|
@JoinColumn(name = "loan_transaction_strategy_id", nullable = true)
|
||||||
private LoanTransactionProcessingStrategy transactionProcessingStrategy;
|
private LoanTransactionProcessingStrategy transactionProcessingStrategy;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Column(name = "name", nullable = false)
|
@Column(name = "name", nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
@Column(name = "description")
|
@Column(name = "description")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@@ -56,19 +59,28 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
@Embedded
|
@Embedded
|
||||||
private final LoanProductRelatedDetail loanProductRelatedDetail;
|
private final LoanProductRelatedDetail loanProductRelatedDetail;
|
||||||
|
|
||||||
public LoanProduct() {
|
protected LoanProduct() {
|
||||||
this.fund = null;
|
this.loanProductRelatedDetail = null;
|
||||||
this.name = null;
|
|
||||||
this.description = null;
|
|
||||||
this.loanProductRelatedDetail = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanProduct(final Fund fund, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
public LoanProduct(
|
||||||
final String name, final String description, final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
|
final Fund fund,
|
||||||
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate,
|
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||||
final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
|
final String name,
|
||||||
final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, final AmortizationMethod amortizationMethod,
|
final String description,
|
||||||
final BigDecimal inArrearsTolerance, final Set<Charge> charges) {
|
final MonetaryCurrency currency,
|
||||||
|
final BigDecimal defaultPrincipal,
|
||||||
|
final BigDecimal defaultNominalInterestRatePerPeriod,
|
||||||
|
final PeriodFrequencyType interestPeriodFrequencyType,
|
||||||
|
final BigDecimal defaultAnnualNominalInterestRate,
|
||||||
|
final InterestMethod interestMethod,
|
||||||
|
final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
|
||||||
|
final Integer repayEvery,
|
||||||
|
final PeriodFrequencyType repaymentFrequencyType,
|
||||||
|
final Integer defaultNumberOfInstallments,
|
||||||
|
final AmortizationMethod amortizationMethod,
|
||||||
|
final BigDecimal inArrearsTolerance,
|
||||||
|
final Set<Charge> charges) {
|
||||||
this.fund = fund;
|
this.fund = fund;
|
||||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||||
this.name = name.trim();
|
this.name = name.trim();
|
||||||
@@ -92,64 +104,15 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return this.loanProductRelatedDetail.getCurrency();
|
return this.loanProductRelatedDetail.getCurrency();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getInArrearsTolerance() {
|
|
||||||
return this.loanProductRelatedDetail.getInArrearsTolerance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return this.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getRepayEvery() {
|
|
||||||
return this.loanProductRelatedDetail.getRepayEvery();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getDefaultNominalInterestRatePerPeriod() {
|
|
||||||
return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PeriodFrequencyType getInterestPeriodFrequencyType() {
|
|
||||||
return this.loanProductRelatedDetail.getInterestPeriodFrequencyType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getDefaultAnnualNominalInterestRate() {
|
|
||||||
return this.loanProductRelatedDetail.getAnnualNominalInterestRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public InterestMethod getInterestMethod() {
|
|
||||||
return this.loanProductRelatedDetail.getInterestMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
|
|
||||||
return this.loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getDefaultNumberOfRepayments() {
|
|
||||||
return this.loanProductRelatedDetail.getNumberOfRepayments();
|
|
||||||
}
|
|
||||||
|
|
||||||
public AmortizationMethod getAmortizationMethod() {
|
|
||||||
return this.loanProductRelatedDetail.getAmortizationMethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Fund getFund() {
|
|
||||||
return fund;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanTransactionProcessingStrategy getTransactionProcessingStrategy() {
|
|
||||||
return transactionProcessingStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Charge> getCharges() {
|
public Set<Charge> getCharges() {
|
||||||
return charges;
|
return charges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(final LoanProductCommand command, final Fund fund, final LoanTransactionProcessingStrategy strategy,
|
public void update(
|
||||||
final Set<Charge> charges) {
|
final LoanProductCommand command,
|
||||||
|
final Fund fund,
|
||||||
|
final LoanTransactionProcessingStrategy strategy,
|
||||||
|
final Set<Charge> charges) {
|
||||||
|
|
||||||
if (command.isNameChanged()) {
|
if (command.isNameChanged()) {
|
||||||
this.name = command.getName();
|
this.name = command.getName();
|
||||||
|
|||||||
+7
-13
@@ -57,21 +57,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
@Column(name = "amortization_method_enum", nullable = false)
|
@Column(name = "amortization_method_enum", nullable = false)
|
||||||
private AmortizationMethod amortizationMethod;
|
private AmortizationMethod amortizationMethod;
|
||||||
|
|
||||||
@Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = false)
|
@Column(name = "arrearstolerance_amount", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal inArrearsTolerance;
|
private BigDecimal inArrearsTolerance;
|
||||||
|
|
||||||
protected LoanProductRelatedDetail() {
|
protected LoanProductRelatedDetail() {
|
||||||
this.principal = null;
|
//
|
||||||
this.nominalInterestRatePerPeriod = null;
|
|
||||||
this.interestPeriodFrequencyType = null;
|
|
||||||
this.annualNominalInterestRate = null;
|
|
||||||
this.interestMethod = null;
|
|
||||||
|
|
||||||
this.repayEvery = null;
|
|
||||||
this.repaymentPeriodFrequencyType = null;
|
|
||||||
this.numberOfRepayments = null;
|
|
||||||
this.amortizationMethod = null;
|
|
||||||
this.inArrearsTolerance = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanProductRelatedDetail(final MonetaryCurrency currency,
|
public LoanProductRelatedDetail(final MonetaryCurrency currency,
|
||||||
@@ -97,7 +87,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
this.repaymentPeriodFrequencyType = repaymentFrequencyType;
|
this.repaymentPeriodFrequencyType = repaymentFrequencyType;
|
||||||
this.numberOfRepayments = defaultNumberOfRepayments;
|
this.numberOfRepayments = defaultNumberOfRepayments;
|
||||||
this.amortizationMethod = amortizationMethod;
|
this.amortizationMethod = amortizationMethod;
|
||||||
this.inArrearsTolerance = inArrearsTolerance;
|
if (inArrearsTolerance != null && BigDecimal.ZERO.compareTo(inArrearsTolerance) == 0) {
|
||||||
|
this.inArrearsTolerance = null;
|
||||||
|
} else {
|
||||||
|
this.inArrearsTolerance = inArrearsTolerance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MonetaryCurrency getCurrency() {
|
public MonetaryCurrency getCurrency() {
|
||||||
|
|||||||
+11
-3
@@ -37,13 +37,13 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = false)
|
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal principalPortion = BigDecimal.ZERO;
|
private BigDecimal principalPortion = BigDecimal.ZERO;
|
||||||
|
|
||||||
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = false)
|
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal interestPortion = BigDecimal.ZERO;
|
private BigDecimal interestPortion = BigDecimal.ZERO;
|
||||||
|
|
||||||
@Column(name = "charges_portion_derived", scale = 6, precision = 19, nullable = false)
|
@Column(name = "charges_portion_derived", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal chargesPortion = BigDecimal.ZERO;
|
private BigDecimal chargesPortion = BigDecimal.ZERO;
|
||||||
|
|
||||||
@Temporal(TemporalType.DATE)
|
@Temporal(TemporalType.DATE)
|
||||||
@@ -218,4 +218,12 @@ public class LoanTransaction extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
this.interestPortion = null;
|
this.interestPortion = null;
|
||||||
this.chargesPortion = null;
|
this.chargesPortion = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGreaterThanZero(final MonetaryCurrency currency) {
|
||||||
|
return getAmount(currency).isGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNotZero(final MonetaryCurrency currency) {
|
||||||
|
return !getAmount(currency).isZero();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -25,7 +25,7 @@ public class AdjustLoanTransactionCommandValidator {
|
|||||||
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
|
baseDataValidator.reset().parameter("loanId").value(command.getLoanId()).notNull().integerGreaterThanZero();
|
||||||
baseDataValidator.reset().parameter("transactionId").value(command.getTransactionId()).notNull().integerGreaterThanZero();
|
baseDataValidator.reset().parameter("transactionId").value(command.getTransactionId()).notNull().integerGreaterThanZero();
|
||||||
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).notNull();
|
baseDataValidator.reset().parameter("transactionDate").value(command.getTransactionDate()).notNull();
|
||||||
baseDataValidator.reset().parameter("transactionAmount").value(command.getTransactionAmount()).notNull().positiveAmount();
|
baseDataValidator.reset().parameter("transactionAmount").value(command.getTransactionAmount()).notNull().zeroOrPositiveAmount();
|
||||||
baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000);
|
baseDataValidator.reset().parameter("note").value(command.getNote()).notExceedingLengthOf(1000);
|
||||||
|
|
||||||
if (!dataValidationErrors.isEmpty()) {
|
if (!dataValidationErrors.isEmpty()) {
|
||||||
|
|||||||
+6
-4
@@ -24,6 +24,7 @@ import org.mifosng.platform.client.domain.Client;
|
|||||||
import org.mifosng.platform.client.domain.ClientRepository;
|
import org.mifosng.platform.client.domain.ClientRepository;
|
||||||
import org.mifosng.platform.client.domain.Note;
|
import org.mifosng.platform.client.domain.Note;
|
||||||
import org.mifosng.platform.client.domain.NoteRepository;
|
import org.mifosng.platform.client.domain.NoteRepository;
|
||||||
|
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||||
import org.mifosng.platform.currency.domain.Money;
|
import org.mifosng.platform.currency.domain.Money;
|
||||||
import org.mifosng.platform.exceptions.ChargeIsNotActiveException;
|
import org.mifosng.platform.exceptions.ChargeIsNotActiveException;
|
||||||
import org.mifosng.platform.exceptions.ChargeNotFoundException;
|
import org.mifosng.platform.exceptions.ChargeNotFoundException;
|
||||||
@@ -487,9 +488,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
throw new LoanTransactionNotFoundException(command.getTransactionId());
|
throw new LoanTransactionNotFoundException(command.getTransactionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Money transactionAmount = Money.of(loan
|
final MonetaryCurrency currency = loan.repaymentScheduleDetail().getPrincipal().getCurrency();
|
||||||
.repaymentScheduleDetail().getPrincipal()
|
final Money transactionAmount = Money.of(currency, command.getTransactionAmount());
|
||||||
.getCurrency(), command.getTransactionAmount());
|
|
||||||
|
|
||||||
// adjustment is only supported for repayments and waivers at present
|
// adjustment is only supported for repayments and waivers at present
|
||||||
LocalDate transactionDate = command.getTransactionDate();
|
LocalDate transactionDate = command.getTransactionDate();
|
||||||
@@ -500,7 +500,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine());
|
loan.adjustExistingTransaction(transactionToAdjust, newTransactionDetail, defaultLoanLifecycleStateMachine());
|
||||||
|
|
||||||
this.loanTransactionRepository.save(newTransactionDetail);
|
if (newTransactionDetail.isGreaterThanZero(currency)) {
|
||||||
|
this.loanTransactionRepository.save(newTransactionDetail);
|
||||||
|
}
|
||||||
|
|
||||||
this.loanRepository.save(loan);
|
this.loanRepository.save(loan);
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -64,12 +64,14 @@ public class LoanProductCommandValidator {
|
|||||||
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).notNull().inMinMaxRange(0, 6);
|
baseDataValidator.reset().parameter("digitsAfterDecimal").value(command.getDigitsAfterDecimal()).notNull().inMinMaxRange(0, 6);
|
||||||
|
|
||||||
baseDataValidator.reset().parameter("principal").value(command.getPrincipal()).notNull().positiveAmount();
|
baseDataValidator.reset().parameter("principal").value(command.getPrincipal()).notNull().positiveAmount();
|
||||||
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsTolerance()).notNull().zeroOrPositiveAmount();
|
baseDataValidator.reset().parameter("inArrearsTolerance").value(command.getInArrearsTolerance()).ignoreIfNull().zeroOrPositiveAmount();
|
||||||
|
|
||||||
baseDataValidator.reset().parameter("repaymentFrequencyType").value(command.getRepaymentFrequencyType()).notNull().inMinMaxRange(0, 3);
|
baseDataValidator.reset().parameter("repaymentFrequencyType").value(command.getRepaymentFrequencyType()).notNull().inMinMaxRange(0, 3);
|
||||||
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).notNull().integerGreaterThanZero();
|
baseDataValidator.reset().parameter("repaymentEvery").value(command.getRepaymentEvery()).notNull().integerGreaterThanZero();
|
||||||
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).notNull().integerGreaterThanZero();
|
baseDataValidator.reset().parameter("numberOfRepayments").value(command.getNumberOfRepayments()).notNull().integerGreaterThanZero();
|
||||||
|
|
||||||
|
// transactionProcessingStrategyId defaults to mifos strategy if left null
|
||||||
|
|
||||||
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriod()).notNull();
|
baseDataValidator.reset().parameter("interestRatePerPeriod").value(command.getInterestRatePerPeriod()).notNull();
|
||||||
baseDataValidator.reset().parameter("interestRateFrequencyType").value(command.getInterestRateFrequencyType()).notNull().inMinMaxRange(0, 3);
|
baseDataValidator.reset().parameter("interestRateFrequencyType").value(command.getInterestRateFrequencyType()).notNull().inMinMaxRange(0, 3);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user