mifosx-58: handle charges at disbursement for loan creation.
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
|||||||
|
ALTER TABLE `m_loan_charge`
|
||||||
|
ADD COLUMN `calculation_percentage` decimal(19,6) DEFAULT NULL AFTER `charge_calculation_enum`;
|
||||||
|
ALTER TABLE `m_loan_charge`
|
||||||
|
ADD COLUMN `calculation_on_amount` decimal(19,6) DEFAULT NULL AFTER `calculation_percentage`;
|
||||||
|
ALTER TABLE `m_loan_charge`
|
||||||
|
ADD COLUMN `amount_paid_derived` decimal(19,6) DEFAULT NULL AFTER `amount`;
|
||||||
|
ALTER TABLE `m_loan_charge`
|
||||||
|
ADD COLUMN `amount_outstanding_derived` decimal(19,6) NOT NULL default 0 AFTER `amount_paid_derived`;
|
||||||
|
ALTER TABLE `m_loan_charge`
|
||||||
|
ADD COLUMN `is_paid_derived` tinyint(1) NOT NULL default 0 AFTER `amount_outstanding_derived`;
|
||||||
@@ -214,9 +214,11 @@ public class LoansApiResource {
|
|||||||
convenienceDataRequired = true;
|
convenienceDataRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
charges = this.chargeReadPlatformService.retrieveLoanCharges(loanId);
|
if (responseParameters.contains("charges")) {
|
||||||
if (CollectionUtils.isEmpty(charges)) {
|
charges = this.chargeReadPlatformService.retrieveLoanCharges(loanId);
|
||||||
charges = null;
|
if (CollectionUtils.isEmpty(charges)) {
|
||||||
|
charges = null; // set back to null so doesnt appear in JSON is no charges exist.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -67,8 +67,9 @@ public class LoanBasicDetailsData {
|
|||||||
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
|
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
|
||||||
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
|
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
|
||||||
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
|
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
|
||||||
for (ChargeData charge : product.getCharges()){
|
|
||||||
charges.add(new LoanChargeData(null, charge.getId(), charge.getName(), charge.getCurrency(),
|
for (ChargeData charge : product.getCharges()) {
|
||||||
|
charges.add(LoanChargeData.newChargeDetails(charge.getId(), charge.getName(), charge.getCurrency(),
|
||||||
charge.getAmount(), charge.getChargeTimeType(), charge.getChargeCalculationType()));
|
charge.getAmount(), charge.getChargeTimeType(), charge.getChargeCalculationType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,21 @@ public class LoanChargeData {
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final String name;
|
private final String name;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
private final EnumOptionData chargeTimeType;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final EnumOptionData chargeCalculationType;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final BigDecimal percentage;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final BigDecimal amountPercentageAppliedTo;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private final CurrencyData currency;
|
private final CurrencyData currency;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final BigDecimal amount;
|
private final BigDecimal amount;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final EnumOptionData chargeTimeType;
|
private final BigDecimal amountPaid;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final EnumOptionData chargeCalculationType;
|
private final BigDecimal amountOutstanding;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Collection<ChargeData> chargeOptions;
|
private final Collection<ChargeData> chargeOptions;
|
||||||
@@ -30,21 +38,42 @@ public class LoanChargeData {
|
|||||||
return new LoanChargeData(null,null,null,null,null,null,null, chargeOptions);
|
return new LoanChargeData(null,null,null,null,null,null,null, chargeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used when populating with details from charge definition (for crud on charges)
|
||||||
|
*/
|
||||||
|
public static LoanChargeData newChargeDetails(
|
||||||
|
final Long chargeId,
|
||||||
|
final String name,
|
||||||
|
final CurrencyData currency,
|
||||||
|
final BigDecimal value,
|
||||||
|
final EnumOptionData chargeTimeType,
|
||||||
|
final EnumOptionData chargeCalculationType) {
|
||||||
|
return new LoanChargeData(null, chargeId, name, currency, value, chargeTimeType, chargeCalculationType, null);
|
||||||
|
}
|
||||||
|
|
||||||
public LoanChargeData(
|
public LoanChargeData(
|
||||||
final Long id,
|
final Long id,
|
||||||
final Long chargeId,
|
final Long chargeId,
|
||||||
final String name,
|
final String name,
|
||||||
final CurrencyData currency,
|
final CurrencyData currency,
|
||||||
final BigDecimal amount,
|
final BigDecimal amount,
|
||||||
|
final BigDecimal amountPaid,
|
||||||
|
final BigDecimal amountOutstanding,
|
||||||
final EnumOptionData chargeTimeType,
|
final EnumOptionData chargeTimeType,
|
||||||
final EnumOptionData chargeCalculationType) {
|
final EnumOptionData chargeCalculationType,
|
||||||
|
final BigDecimal percentage,
|
||||||
|
final BigDecimal amountPercentageAppliedTo) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.chargeId = chargeId;
|
this.chargeId = chargeId;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
|
this.amountPaid = amountPaid;
|
||||||
|
this.amountOutstanding = amountOutstanding;
|
||||||
this.chargeTimeType = chargeTimeType;
|
this.chargeTimeType = chargeTimeType;
|
||||||
this.chargeCalculationType = chargeCalculationType;
|
this.chargeCalculationType = chargeCalculationType;
|
||||||
|
this.percentage = percentage;
|
||||||
|
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
|
||||||
|
|
||||||
this.chargeOptions = null;
|
this.chargeOptions = null;
|
||||||
}
|
}
|
||||||
@@ -63,8 +92,12 @@ public class LoanChargeData {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
|
this.amountPaid = BigDecimal.ZERO;
|
||||||
|
this.amountOutstanding = amount;
|
||||||
this.chargeTimeType = chargeTimeType;
|
this.chargeTimeType = chargeTimeType;
|
||||||
this.chargeCalculationType = chargeCalculationType;
|
this.chargeCalculationType = chargeCalculationType;
|
||||||
|
this.percentage = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
this.chargeOptions = chargeOptions;
|
this.chargeOptions = chargeOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
-19
@@ -195,9 +195,12 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
|
|||||||
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
|
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
|
||||||
|
|
||||||
public String loanChargeSchema(){
|
public String loanChargeSchema(){
|
||||||
return "lc.id as id, c.id as chargeId, c.name as name, lc.amount as amount, c.currency_code as currencyCode, " +
|
return "lc.id as id, c.id as chargeId, c.name as name, " +
|
||||||
"lc.charge_time_enum as chargeTime, " +
|
"lc.amount as amountDue, lc.amount_paid_derived as amountPaid, lc.amount_outstanding_derived as amountOutstanding, " +
|
||||||
"lc.charge_calculation_enum as chargeCalculation, oc.name as currencyName, " +
|
"lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " +
|
||||||
|
"lc.charge_time_enum as chargeTime, " +
|
||||||
|
"lc.charge_calculation_enum as chargeCalculation, " +
|
||||||
|
"c.currency_code as currencyCode, oc.name as currencyName, " +
|
||||||
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " +
|
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " +
|
||||||
"oc.internationalized_name_code as currencyNameCode from m_charge c " +
|
"oc.internationalized_name_code as currencyNameCode from m_charge c " +
|
||||||
"join m_organisation_currency oc on c.currency_code = oc.code " +
|
"join m_organisation_currency oc on c.currency_code = oc.code " +
|
||||||
@@ -205,28 +208,37 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanChargeData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
|
public LoanChargeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||||
Long id = rs.getLong("id");
|
|
||||||
Long chargeId = rs.getLong("chargeId");
|
|
||||||
String name = rs.getString("name");
|
|
||||||
BigDecimal amount = rs.getBigDecimal("amount");
|
|
||||||
|
|
||||||
String currencyCode = rs.getString("currencyCode");
|
final Long id = rs.getLong("id");
|
||||||
String currencyName = rs.getString("currencyName");
|
final Long chargeId = rs.getLong("chargeId");
|
||||||
String currencyNameCode = rs.getString("currencyNameCode");
|
final String name = rs.getString("name");
|
||||||
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
final BigDecimal amount = rs.getBigDecimal("amountDue");
|
||||||
Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
|
BigDecimal amountPaid = rs.getBigDecimal("amountPaid");
|
||||||
|
if (amountPaid == null) {
|
||||||
|
amountPaid = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding");
|
||||||
|
|
||||||
CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces,
|
final BigDecimal percentageOf = rs.getBigDecimal("percentageOf");
|
||||||
|
final BigDecimal amountPercentageAppliedTo = rs.getBigDecimal("amountPercentageAppliedTo");
|
||||||
|
|
||||||
|
final String currencyCode = rs.getString("currencyCode");
|
||||||
|
final String currencyName = rs.getString("currencyName");
|
||||||
|
final String currencyNameCode = rs.getString("currencyNameCode");
|
||||||
|
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
|
||||||
|
final Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
|
||||||
|
|
||||||
|
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces,
|
||||||
currencyDisplaySymbol, currencyNameCode);
|
currencyDisplaySymbol, currencyNameCode);
|
||||||
|
|
||||||
int chargeTime = rs.getInt("chargeTime");
|
final int chargeTime = rs.getInt("chargeTime");
|
||||||
EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
|
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
|
||||||
|
|
||||||
int chargeCalculation = rs.getInt("chargeCalculation");
|
final int chargeCalculation = rs.getInt("chargeCalculation");
|
||||||
EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
|
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
|
||||||
|
|
||||||
return new LoanChargeData(id, chargeId, name, currency, amount, chargeTimeType, chargeCalculationType);
|
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountOutstanding, chargeTimeType, chargeCalculationType, percentageOf, amountPercentageAppliedTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -491,14 +491,17 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
// add repayment transaction to track incoming money from client to mfi for (charges due at time of disbursement)
|
// add repayment transaction to track incoming money from client to mfi for (charges due at time of disbursement)
|
||||||
if (getTotalChargesDueAtDisbursement().isGreaterThanZero()) {
|
if (getTotalChargesDueAtDisbursement().isGreaterThanZero()) {
|
||||||
|
|
||||||
// FIXME - add charges component to 'transactions'
|
|
||||||
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn);
|
LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getTotalChargesDueAtDisbursement(), disbursedOn);
|
||||||
Money zero = Money.zero(getCurrency());
|
Money zero = Money.zero(getCurrency());
|
||||||
chargesPayment.updateComponents(zero, zero, zero, getTotalChargesDueAtDisbursement());
|
chargesPayment.updateComponents(zero, zero, zero, getTotalChargesDueAtDisbursement());
|
||||||
chargesPayment.updateLoan(this);
|
chargesPayment.updateLoan(this);
|
||||||
this.loanTransactions.add(chargesPayment);
|
this.loanTransactions.add(chargesPayment);
|
||||||
|
|
||||||
// should also pay-off the time-of-disbursement charges
|
for (LoanCharge charge : this.charges) {
|
||||||
|
if (charge.isDueAtDisbursement()) {
|
||||||
|
charge.markAsFullyPaid();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disbursedOn.isBefore(getApprovedOnDate())) {
|
if (disbursedOn.isBefore(getApprovedOnDate())) {
|
||||||
@@ -515,8 +518,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
"cannot.be.a.future.date", errorMessage, disbursedOn);
|
"cannot.be.a.future.date", errorMessage, disbursedOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalDate firstRepaymentDueDate = this.repaymentScheduleInstallments
|
LocalDate firstRepaymentDueDate = this.repaymentScheduleInstallments.get(0).getDueDate();
|
||||||
.get(0).getDueDate();
|
|
||||||
if (disbursedOn.isAfter(firstRepaymentDueDate)) {
|
if (disbursedOn.isAfter(firstRepaymentDueDate)) {
|
||||||
final String errorMessage = "The date on which a loan is disbursed cannot be after the first expected repayment date: "
|
final String errorMessage = "The date on which a loan is disbursed cannot be after the first expected repayment date: "
|
||||||
+ firstRepaymentDueDate.toString();
|
+ firstRepaymentDueDate.toString();
|
||||||
@@ -526,14 +528,19 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void undoDisbursal(
|
public void undoDisbursal(final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
|
||||||
|
|
||||||
LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, LoanStatus.fromInt(this.loanStatus));
|
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSAL_UNDO, LoanStatus.fromInt(this.loanStatus));
|
||||||
this.loanStatus = statusEnum.getValue();
|
this.loanStatus = statusEnum.getValue();
|
||||||
|
|
||||||
this.loanTransactions.clear();
|
this.loanTransactions.clear();
|
||||||
this.disbursedOnDate = null;
|
this.disbursedOnDate = null;
|
||||||
|
|
||||||
|
for (LoanCharge charge : this.charges) {
|
||||||
|
if (charge.isDueAtDisbursement()) {
|
||||||
|
charge.resetToOriginal();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void waive(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
public void waive(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.mifosng.platform.loan.domain;
|
package org.mifosng.platform.loan.domain;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -30,21 +31,41 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
@JoinColumn(name = "charge_id", referencedColumnName = "id", nullable=false)
|
@JoinColumn(name = "charge_id", referencedColumnName = "id", nullable=false)
|
||||||
private Charge charge;
|
private Charge charge;
|
||||||
|
|
||||||
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
|
||||||
private BigDecimal amount;
|
|
||||||
|
|
||||||
@Column(name = "charge_time_enum", nullable = false)
|
@Column(name = "charge_time_enum", nullable = false)
|
||||||
private Integer chargeTime;
|
private Integer chargeTime;
|
||||||
|
|
||||||
@Column(name = "charge_calculation_enum")
|
@Column(name = "charge_calculation_enum")
|
||||||
private Integer chargeCalculation;
|
private Integer chargeCalculation;
|
||||||
|
|
||||||
|
@Column(name = "calculation_percentage", scale = 6, precision = 19, nullable = true)
|
||||||
|
private BigDecimal percentage;
|
||||||
|
|
||||||
|
@Column(name = "calculation_on_amount", scale = 6, precision = 19, nullable = true)
|
||||||
|
private BigDecimal amountPercentageAppliedTo;
|
||||||
|
|
||||||
|
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
@Column(name = "amount_paid_derived", scale = 6, precision = 19, nullable = true)
|
||||||
|
private BigDecimal amountPaid;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false)
|
||||||
|
private BigDecimal amountOutstanding;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Column(name = "is_paid_derived", nullable=false)
|
||||||
|
private boolean paid = false;
|
||||||
|
|
||||||
public static LoanCharge createNew(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) {
|
public static LoanCharge createNew(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) {
|
||||||
return new LoanCharge(loan, chargeDefinition, command);
|
return new LoanCharge(loan, chargeDefinition, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanCharge createNew(final Charge chargeDefinition, final LoanChargeCommand command) {
|
/*
|
||||||
return new LoanCharge(null, chargeDefinition, command);
|
* loanPrincipal is required for charges that are percentage based
|
||||||
|
*/
|
||||||
|
public static LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final LoanChargeCommand command, final BigDecimal loanPrincipal) {
|
||||||
|
return new LoanCharge(chargeDefinition, command, loanPrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanCharge createNew(final Charge chargeDefinition) {
|
public static LoanCharge createNew(final Charge chargeDefinition) {
|
||||||
@@ -55,16 +76,36 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LoanCharge(final Charge chargeDefinition, final LoanChargeCommand command, final BigDecimal loanPrincipal) {
|
||||||
|
this.loan = null;
|
||||||
|
this.charge = chargeDefinition;
|
||||||
|
|
||||||
|
if (command.isChargeTimeTypeChanged()){
|
||||||
|
this.chargeTime = command.getChargeTimeType();
|
||||||
|
} else {
|
||||||
|
this.chargeTime = chargeDefinition.getChargeTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.isChargeCalculationTypeChanged()){
|
||||||
|
this.chargeCalculation = command.getChargeCalculationType();
|
||||||
|
} else {
|
||||||
|
this.chargeCalculation = chargeDefinition.getChargeCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal chargeAmount = chargeDefinition.getAmount();
|
||||||
|
if (command.isAmountChanged()){
|
||||||
|
chargeAmount = command.getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
populateDerivedFields(loanPrincipal, chargeAmount);
|
||||||
|
this.paid = determineIfFullyPaid();
|
||||||
|
}
|
||||||
|
|
||||||
private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) {
|
private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) {
|
||||||
this.loan = loan;
|
this.loan = loan;
|
||||||
this.charge = chargeDefinition;
|
this.charge = chargeDefinition;
|
||||||
|
|
||||||
if (command.isAmountChanged()){
|
|
||||||
this.amount = command.getAmount();
|
|
||||||
} else {
|
|
||||||
this.amount = chargeDefinition.getAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.isChargeTimeTypeChanged()){
|
if (command.isChargeTimeTypeChanged()){
|
||||||
this.chargeTime = command.getChargeTimeType();
|
this.chargeTime = command.getChargeTimeType();
|
||||||
} else {
|
} else {
|
||||||
@@ -76,9 +117,58 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
} else {
|
} else {
|
||||||
this.chargeCalculation = chargeDefinition.getChargeCalculation();
|
this.chargeCalculation = chargeDefinition.getChargeCalculation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigDecimal chargeAmount = chargeDefinition.getAmount();
|
||||||
|
if (command.isAmountChanged()){
|
||||||
|
chargeAmount = command.getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
populateDerivedFields(loan.getPrincpal().getAmount(), chargeAmount);
|
||||||
|
this.paid = determineIfFullyPaid();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanCharge(final Loan loan, final Charge chargeDefinition) {
|
private void populateDerivedFields(final BigDecimal loanPrincipal, final BigDecimal chargeAmount) {
|
||||||
|
|
||||||
|
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
|
||||||
|
case INVALID:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = null;
|
||||||
|
break;
|
||||||
|
case FLAT:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = chargeAmount;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = chargeAmount;
|
||||||
|
break;
|
||||||
|
case PERCENT_OF_AMOUNT:
|
||||||
|
this.percentage = chargeAmount;
|
||||||
|
this.amountPercentageAppliedTo = loanPrincipal;
|
||||||
|
this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = calculateOutstanding();
|
||||||
|
break;
|
||||||
|
case PERCENT_OF_AMOUNT_AND_INTEREST:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = null;
|
||||||
|
break;
|
||||||
|
case PERCENT_OF_INTEREST:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoanCharge(final Loan loan, final Charge chargeDefinition) {
|
||||||
this(loan, chargeDefinition, chargeDefinition.getAmount(), chargeDefinition.getChargeTime(), chargeDefinition.getChargeCalculation());
|
this(loan, chargeDefinition, chargeDefinition.getAmount(), chargeDefinition.getChargeTime(), chargeDefinition.getChargeCalculation());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +197,6 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
if (command.isChargeCalculationTypeChanged()){
|
if (command.isChargeCalculationTypeChanged()){
|
||||||
this.chargeCalculation = ChargeCalculationType.fromInt(command.getChargeCalculationType()).getValue();
|
this.chargeCalculation = ChargeCalculationType.fromInt(command.getChargeCalculationType()).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDueAtDisbursement() {
|
public boolean isDueAtDisbursement() {
|
||||||
@@ -117,18 +206,36 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
public BigDecimal calculateMonetaryAmount(final Money principalDisbursed) {
|
public BigDecimal calculateMonetaryAmount(final Money principalDisbursed) {
|
||||||
BigDecimal calculatedAmount = BigDecimal.ZERO;
|
BigDecimal calculatedAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
if (ChargeCalculationType.fromInt(chargeCalculation).equals(ChargeCalculationType.FLAT)) {
|
switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
|
||||||
calculatedAmount = this.amount;
|
case INVALID:
|
||||||
} else if (ChargeCalculationType.fromInt(chargeCalculation).equals(ChargeCalculationType.PERCENT_OF_AMOUNT)) {
|
calculatedAmount = this.amount;
|
||||||
|
break;
|
||||||
BigDecimal amountAsPercentageFactor = BigDecimal.ZERO;
|
case FLAT:
|
||||||
if (isGreaterThanZero(this.amount)) {
|
calculatedAmount = this.amount;
|
||||||
amountAsPercentageFactor = this.amount.divide(BigDecimal.valueOf(Double.valueOf("100")));
|
break;
|
||||||
|
case PERCENT_OF_AMOUNT:
|
||||||
|
this.amountPercentageAppliedTo = principalDisbursed.getAmount();
|
||||||
|
this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = calculateOutstanding();
|
||||||
|
calculatedAmount = this.amount;
|
||||||
|
break;
|
||||||
|
case PERCENT_OF_AMOUNT_AND_INTEREST:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = null;
|
||||||
|
break;
|
||||||
|
case PERCENT_OF_INTEREST:
|
||||||
|
this.percentage = null;
|
||||||
|
this.amount = null;
|
||||||
|
this.amountPercentageAppliedTo = null;
|
||||||
|
this.amountPaid = null;
|
||||||
|
this.amountOutstanding = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedAmount = principalDisbursed.multiplyRetainScale(amountAsPercentageFactor, RoundingMode.HALF_EVEN).getAmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
return calculatedAmount;
|
return calculatedAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,4 +252,43 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
|||||||
return new LoanChargeCommand(modifiedParameters, this.getId(), this.loan.getId(), this.charge.getId(),
|
return new LoanChargeCommand(modifiedParameters, this.getId(), this.loan.getId(), this.charge.getId(),
|
||||||
this.amount, this.chargeTime, this.chargeCalculation);
|
this.amount, this.chargeTime, this.chargeCalculation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean determineIfFullyPaid() {
|
||||||
|
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calculateOutstanding() {
|
||||||
|
|
||||||
|
BigDecimal amountPaidLocal = BigDecimal.ZERO;
|
||||||
|
if (this.amountPaid != null) {
|
||||||
|
amountPaidLocal = this.amountPaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.amount.subtract(amountPaidLocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage) {
|
||||||
|
|
||||||
|
BigDecimal percentageOf = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
if (isGreaterThanZero(value)) {
|
||||||
|
final MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||||
|
BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc);
|
||||||
|
percentageOf = value.multiply(multiplicand, mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return percentageOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markAsFullyPaid() {
|
||||||
|
this.amountPaid = this.amount;
|
||||||
|
this.amountOutstanding = BigDecimal.ZERO;
|
||||||
|
this.paid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetToOriginal() {
|
||||||
|
this.amountPaid = BigDecimal.ZERO;
|
||||||
|
this.amountOutstanding = this.amount;
|
||||||
|
this.paid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -68,7 +68,7 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
|
|||||||
|
|
||||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode());
|
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode());
|
||||||
|
|
||||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges());
|
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanScheduleRelatedDetails.getPrincipal().getAmount());
|
||||||
|
|
||||||
return loanScheduleGenerator.generate(applicationCurrency,
|
return loanScheduleGenerator.generate(applicationCurrency,
|
||||||
loanScheduleRelatedDetails, loanTermFrequency,
|
loanScheduleRelatedDetails, loanTermFrequency,
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class LoanAssembler {
|
|||||||
Staff loanOfficer= findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
|
Staff loanOfficer= findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
|
||||||
|
|
||||||
// optionally, see if charges are associated with loan on creation (through loan product or by being directly added)
|
// optionally, see if charges are associated with loan on creation (through loan product or by being directly added)
|
||||||
final Set<LoanCharge> loanCharges = assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loanProduct.getCurrency().getCode());
|
final Set<LoanCharge> loanCharges = assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loanProduct.getCurrency().getCode(), command.getPrincipal());
|
||||||
|
|
||||||
Loan loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
|
Loan loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
|
||||||
loan.setExternalId(command.getExternalId());
|
loan.setExternalId(command.getExternalId());
|
||||||
@@ -130,8 +130,11 @@ public class LoanAssembler {
|
|||||||
return loan;
|
return loan;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<LoanCharge> assembleSetOfLoanCharges(LoanChargeCommand[] chargesPassedAtCreation,
|
public Set<LoanCharge> assembleSetOfLoanCharges(final LoanChargeCommand[] chargesPassedAtCreation,
|
||||||
Set<Charge> chargesInheritedFromProduct, String loanCurrencyCode) {
|
final Set<Charge> chargesInheritedFromProduct,
|
||||||
|
final String loanCurrencyCode,
|
||||||
|
final BigDecimal loanPrincipal) {
|
||||||
|
|
||||||
Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
|
Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
|
||||||
|
|
||||||
if (!ObjectUtils.isEmpty(chargesPassedAtCreation)) {
|
if (!ObjectUtils.isEmpty(chargesPassedAtCreation)) {
|
||||||
@@ -152,7 +155,7 @@ public class LoanAssembler {
|
|||||||
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage);
|
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanCharges.add(LoanCharge.createNew(chargeDefinition, loanChargeCommand));
|
loanCharges.add(LoanCharge.createNewWithoutLoan(chargeDefinition, loanChargeCommand, loanPrincipal));
|
||||||
}
|
}
|
||||||
} else if (chargesPassedAtCreation == null) {
|
} else if (chargesPassedAtCreation == null) {
|
||||||
for (Charge productCharge : chargesInheritedFromProduct) {
|
for (Charge productCharge : chargesInheritedFromProduct) {
|
||||||
|
|||||||
+15
-6
@@ -1,5 +1,6 @@
|
|||||||
package org.mifosng.platform.loan.service;
|
package org.mifosng.platform.loan.service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ import org.mifosng.platform.api.commands.LoanChargeCommand;
|
|||||||
import org.mifosng.platform.charge.domain.Charge;
|
import org.mifosng.platform.charge.domain.Charge;
|
||||||
import org.mifosng.platform.charge.domain.ChargeRepository;
|
import org.mifosng.platform.charge.domain.ChargeRepository;
|
||||||
import org.mifosng.platform.loan.domain.LoanCharge;
|
import org.mifosng.platform.loan.domain.LoanCharge;
|
||||||
|
import org.mifosng.platform.loan.domain.LoanChargeRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -14,22 +16,29 @@ import org.springframework.stereotype.Service;
|
|||||||
public class LoanChargeAssembler {
|
public class LoanChargeAssembler {
|
||||||
|
|
||||||
private ChargeRepository chargeRepository;
|
private ChargeRepository chargeRepository;
|
||||||
|
private final LoanChargeRepository loanChargeRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public LoanChargeAssembler(final ChargeRepository chargeRepository) {
|
public LoanChargeAssembler(final ChargeRepository chargeRepository, final LoanChargeRepository loanChargeRepository) {
|
||||||
this.chargeRepository = chargeRepository;
|
this.chargeRepository = chargeRepository;
|
||||||
|
this.loanChargeRepository = loanChargeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<LoanCharge> assembleFrom(final LoanChargeCommand[] charges) {
|
public Set<LoanCharge> assembleFrom(final LoanChargeCommand[] charges, final BigDecimal loanPrincipal) {
|
||||||
|
|
||||||
final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
|
final Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
|
||||||
|
|
||||||
// FIXME - KW - what if loanChargeCommands contains the actual loanCharge id, use loanChargeRepository to fetch it then.
|
|
||||||
if (charges != null) {
|
if (charges != null) {
|
||||||
for (LoanChargeCommand loanChargeCommand : charges) {
|
for (LoanChargeCommand loanChargeCommand : charges) {
|
||||||
Charge chargeDefinition = this.chargeRepository.findOne(loanChargeCommand.getChargeId());
|
|
||||||
LoanCharge loanCharge = LoanCharge.createNew(chargeDefinition, loanChargeCommand);
|
if (loanChargeCommand.getId() == null) {
|
||||||
loanCharges.add(loanCharge);
|
final Charge chargeDefinition = this.chargeRepository.findOne(loanChargeCommand.getChargeId());
|
||||||
|
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition, loanChargeCommand, loanPrincipal);
|
||||||
|
loanCharges.add(loanCharge);
|
||||||
|
} else {
|
||||||
|
final LoanCharge loanCharge = this.loanChargeRepository.findOne(loanChargeCommand.getId());
|
||||||
|
loanCharges.add(loanCharge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -156,7 +156,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
Fund fund = this.loanAssembler.findFundByIdIfProvided(command.getFundId());
|
Fund fund = this.loanAssembler.findFundByIdIfProvided(command.getFundId());
|
||||||
Staff loanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
|
Staff loanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
|
||||||
LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
|
LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
|
||||||
Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loan.getCurrency().getCode());
|
Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loan.getCurrency().getCode(), loan.getPrincpal().getAmount());
|
||||||
|
|
||||||
final LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
|
final LoanScheduleNewData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
|
||||||
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer);
|
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer);
|
||||||
|
|||||||
Reference in New Issue
Block a user