MIFOSX-57 reorganization of loan charges design.

This commit is contained in:
Michal Dudzinski
2012-09-18 15:31:40 +02:00
parent 725011c1ae
commit 055e6cde70
15 changed files with 191 additions and 75 deletions
@@ -0,0 +1,13 @@
DROP TABLE `m_loan_charge`;
CREATE TABLE `m_loan_charge` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`loan_id` bigint(20) NOT NULL,
`charge_id` bigint(20) NOT NULL,
`charge_time_enum` smallint(5) NOT NULL,
`charge_calculation_enum` smallint(5) NOT NULL,
`amount` decimal(19,6) NOT NULL,
PRIMARY KEY (`id`),
KEY `charge_id` (`charge_id`),
CONSTRAINT `m_loan_charge_ibfk_1` FOREIGN KEY (`charge_id`) REFERENCES `m_charge` (`id`),
CONSTRAINT `m_loan_charge_ibfk_2` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -201,6 +201,23 @@ public class DataValidatorBuilder {
}
return this;
}
public DataValidatorBuilder longGreaterThanZero() {
if (value == null && ignoreNullValue) {
return this;
}
if (value != null) {
Long number = Long.valueOf(value.toString());
if (number < 1) {
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter).append(".not.greater.than.zero");
StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(parameter).append(" must be greater than 0.");
ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), parameter, number, 0);
dataValidationErrors.add(error);
}
}
return this;
}
public DataValidatorBuilder arrayNotEmpty() {
if (value == null && ignoreNullValue) {
@@ -33,6 +33,7 @@ import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.data.FundData;
import org.mifosng.platform.api.data.LoanAccountData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductLookup;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
@@ -148,12 +149,9 @@ public class LoansApiResource {
ChargeData chargeTemplate = this.chargeReadPlatformService.retrieveLoanChargeTemplate();
LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveClientAndProductDetails(clientId, productId);
if (loanBasicDetails.getCharges() != null) {
chargeOptions.removeAll(loanBasicDetails.getCharges());
}
final boolean convenienceDataRequired = false;
Collection<ChargeData> charges = loanBasicDetails.getCharges();
Collection<LoanChargeData> charges = loanBasicDetails.getCharges();
Collection<StaffData> allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.getClientOfficeId());
@@ -185,7 +183,7 @@ public class LoansApiResource {
Collection<LoanRepaymentTransactionData> loanRepayments = null;
LoanScheduleData repaymentSchedule = null;
LoanPermissionData permissions = null;
Collection<ChargeData> charges = null;
Collection<LoanChargeData> charges = null;
boolean convenienceDataRequired = false;
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
@@ -250,9 +248,6 @@ public class LoansApiResource {
fundOptions = this.fundReadPlatformService.retrieveAllFunds();
repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies();
chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges();
if (charges != null) {
chargeOptions.removeAll(charges);
}
allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.getClientOfficeId());
chargeTemplate = this.chargeReadPlatformService.retrieveLoanChargeTemplate();
}
@@ -6,6 +6,7 @@ import java.util.Set;
public class LoanChargeCommand {
private final Long id;
private final Long chargeId;
private final BigDecimal amount;
private final Integer chargeTimeType;
@@ -13,10 +14,11 @@ public class LoanChargeCommand {
private final Set<String> modifiedParameters;
public LoanChargeCommand(final Set<String> modifiedParameters, final Long id, final BigDecimal amount,
final Integer chargeTimeType, final Integer chargeCalculationType) {
public LoanChargeCommand(final Set<String> modifiedParameters, final Long id, final Long chargeId,
final BigDecimal amount, final Integer chargeTimeType, final Integer chargeCalculationType) {
this.modifiedParameters = modifiedParameters;
this.id = id;
this.chargeId = chargeId;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
@@ -26,6 +28,10 @@ public class LoanChargeCommand {
return id;
}
public Long getChargeId() {
return chargeId;
}
public BigDecimal getAmount() {
return amount;
}
@@ -54,7 +54,7 @@ public class LoanAccountData {
// associations
private final LoanScheduleData repaymentSchedule;
private final Collection<LoanRepaymentTransactionData> loanRepayments;
private final Collection<ChargeData> charges;
private final Collection<LoanChargeData> charges;
private final LoanPermissionData permissions;
private final LoanConvenienceData convenienceData;
@@ -78,7 +78,7 @@ public class LoanAccountData {
final LoanScheduleData repaymentSchedule,
final Collection<LoanRepaymentTransactionData> loanRepayments,
final LoanPermissionData permissions,
final Collection<ChargeData> charges,
final Collection<LoanChargeData> charges,
final Collection<LoanProductLookup> productOptions,
final Collection<EnumOptionData> termFrequencyTypeOptions,
final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
@@ -1,6 +1,7 @@
package org.mifosng.platform.api.data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import org.joda.time.DateTime;
@@ -53,7 +54,7 @@ public class LoanBasicDetailsData {
private final LocalDate expectedMaturityDate;
private final LocalDate lifeCycleStatusDate;
private final Collection<ChargeData> charges;
private final Collection<LoanChargeData> charges;
public static LoanBasicDetailsData populateForNewLoanCreation(final Long clientId, final String clientName, final LocalDate expectedDisbursementDate,
final Long clientOfficeId) {
@@ -64,6 +65,12 @@ public class LoanBasicDetailsData {
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
final Collection<LoanChargeData> charges = new ArrayList<LoanChargeData>();
for (ChargeData charge : product.getCharges()){
charges.add(new LoanChargeData(null, charge.getId(), charge.getName(), charge.getCurrency(),
charge.getAmount(), charge.getChargeTimeType(), charge.getChargeCalculationType()));
}
return new LoanBasicDetailsData(
loanAccount.clientId, loanAccount.clientName, loanAccount.clientOfficeId,
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(),
@@ -71,7 +78,7 @@ public class LoanBasicDetailsData {
termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
product.getTransactionProcessingStrategyId(), product.getAmortizationType(),
product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(), product.getInterestType(), product.getInterestCalculationPeriodType(),
loanAccount.expectedDisbursementDate, product.getCharges());
loanAccount.expectedDisbursementDate, charges);
}
private LoanBasicDetailsData(
@@ -89,7 +96,7 @@ public class LoanBasicDetailsData {
final EnumOptionData interestRateFrequencyType,
final EnumOptionData interestType,
final EnumOptionData interestCalculationPeriodType,
final LocalDate expectedDisbursementDate, final Collection<ChargeData> charges) {
final LocalDate expectedDisbursementDate, final Collection<LoanChargeData> charges) {
this.id = null;
this.externalId = null;
this.clientId = clientId;
@@ -219,7 +226,7 @@ public class LoanBasicDetailsData {
final Integer termFrequency,
final EnumOptionData termPeriodFrequencyType,
final Integer transactionStrategyId,
final Collection<ChargeData> charges,
final Collection<LoanChargeData> charges,
final Long loanOfficerId, String loanOfficerName) {
this.id = id;
this.externalId = externalId;
@@ -490,7 +497,7 @@ public class LoanBasicDetailsData {
return transactionStrategyId;
}
public Collection<ChargeData> getCharges() {
public Collection<LoanChargeData> getCharges() {
return charges;
}
@@ -0,0 +1,58 @@
package org.mifosng.platform.api.data;
import java.math.BigDecimal;
/**
* Immutable data object for loan charge data.
*/
public class LoanChargeData {
private final Long id;
private final Long chargeId;
private final String name;
private final CurrencyData currency;
private final BigDecimal amount;
private final EnumOptionData chargeTimeType;
private final EnumOptionData chargeCalculationType;
public LoanChargeData(Long id, Long chargeId, String name, CurrencyData currency, BigDecimal amount,
EnumOptionData chargeTimeType, EnumOptionData chargeCalculationType) {
this.id = id;
this.chargeId = chargeId;
this.name = name;
this.currency = currency;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
}
public Long getId() {
return id;
}
public Long getChargeId() {
return chargeId;
}
public String getName() {
return name;
}
public CurrencyData getCurrency() {
return currency;
}
public BigDecimal getAmount() {
return amount;
}
public EnumOptionData getChargeTimeType() {
return chargeTimeType;
}
public EnumOptionData getChargeCalculationType() {
return chargeCalculationType;
}
}
@@ -497,25 +497,19 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
charges = new LoanChargeCommand[array.size()];
for (int i=0; i<array.size(); i++) {
JsonObject loanCharge = array.get(i).getAsJsonObject();
Map<String, Object> chargeRequestMap = gsonConverter.fromJson(loanCharge, typeOfMap);
if (requestMap.containsKey("locale")){
chargeRequestMap.put("locale", requestMap.get("locale"));
}
Set<String> chargeModifiedParameters = new HashSet<String>();
Long id = loanCharge.get("id").getAsLong();
BigDecimal amount = null;
Integer chargeTimeType = null;
Integer chargeCalculationType = null;
if (loanCharge.has("amount")){
chargeModifiedParameters.add("amount");
amount = convertFrom(loanCharge.get("amount").getAsString(), "amount", extractLocaleValue(requestMap));
}
if (loanCharge.has("chargeTimeType")){
chargeModifiedParameters.add("chargeTimeType");
chargeTimeType = loanCharge.get("chargeTimeType").getAsInt();
}
if (loanCharge.has("chargeCalculationType")){
chargeModifiedParameters.add("chargeCalculationType");
chargeCalculationType = loanCharge.get("chargeCalculationType").getAsInt();
}
charges[i] = new LoanChargeCommand(chargeModifiedParameters, id, amount, chargeTimeType, chargeCalculationType);
Long chargeId = extractLongParameter("chargeId", chargeRequestMap, chargeModifiedParameters);
BigDecimal amount = extractBigDecimalParameter("amount", chargeRequestMap, chargeModifiedParameters);
Integer chargeTimeType = extractIntegerParameter("chargeTimeType", chargeRequestMap, chargeModifiedParameters);
Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", chargeRequestMap, chargeModifiedParameters);
charges[i] = new LoanChargeCommand(chargeModifiedParameters, null, chargeId, amount,
chargeTimeType, chargeCalculationType);
}
}
}
@@ -709,7 +703,7 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
}
return paramValue;
}
private boolean extractBooleanParameter(final String paramName, final Map<String, ?> requestMap, final Set<String> modifiedParameters) {
boolean paramValue = false;
String paramValueAsString = null;
@@ -1,6 +1,7 @@
package org.mifosng.platform.charge.service;
import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.LoanChargeData;
import java.util.Collection;
@@ -16,7 +17,7 @@ public interface ChargeReadPlatformService {
Collection<ChargeData> retrieveLoanProductCharges(Long loanProductId);
Collection<ChargeData> retrieveLoanCharges(Long loanId);
Collection<LoanChargeData> retrieveLoanCharges(Long loanId);
ChargeData retrieveLoanChargeTemplate();
}
@@ -3,6 +3,7 @@ package org.mifosng.platform.charge.service;
import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.charge.domain.ChargeAppliesTo;
import org.mifosng.platform.charge.domain.ChargeCalculationType;
import org.mifosng.platform.charge.domain.ChargeTimeType;
@@ -106,10 +107,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
}
@Override
public Collection<ChargeData> retrieveLoanCharges(Long loanId) {
public Collection<LoanChargeData> retrieveLoanCharges(Long loanId) {
this.context.authenticatedUser();
ChargeMapper rm = new ChargeMapper();
LoanChargeMapper rm = new LoanChargeMapper();
String sql = "select " + rm.loanChargeSchema() + " where c.is_deleted=0 and lc.loan_id=?";
@@ -159,15 +160,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
return chargeSchema() + " join m_product_loan_charge plc on plc.charge_id = c.id";
}
public String loanChargeSchema(){
return "c.id as id, c.name as name, lc.amount as amount, c.currency_code as currencyCode, " +
"c.charge_applies_to_enum as chargeAppliesTo, lc.charge_time_enum as chargeTime, " +
"lc.charge_calculation_enum as chargeCalculation, is_active as active, oc.name as currencyName, " +
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " +
"oc.internationalized_name_code as currencyNameCode from m_charge c " +
"join m_organisation_currency oc on c.currency_code = oc.code " +
"join m_loan_charge lc on lc.charge_id = c.id ";
}
@Override
public ChargeData mapRow(ResultSet rs, int rowNum) throws SQLException {
@@ -198,4 +191,42 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, active);
}
}
private static final class LoanChargeMapper implements RowMapper<LoanChargeData> {
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, " +
"lc.charge_time_enum as chargeTime, " +
"lc.charge_calculation_enum as chargeCalculation, oc.name as currencyName, " +
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " +
"oc.internationalized_name_code as currencyNameCode from m_charge c " +
"join m_organisation_currency oc on c.currency_code = oc.code " +
"join m_loan_charge lc on lc.charge_id = c.id ";
}
@Override
public LoanChargeData mapRow(ResultSet rs, 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");
String currencyName = rs.getString("currencyName");
String currencyNameCode = rs.getString("currencyNameCode");
String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
Integer currencyDecimalPlaces = JdbcSupport.getInteger(rs, "currencyDecimalPlaces");
CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDecimalPlaces,
currencyDisplaySymbol, currencyNameCode);
int chargeTime = rs.getInt("chargeTime");
EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
int chargeCalculation = rs.getInt("chargeCalculation");
EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
return new LoanChargeData(id, chargeId, name, currency, amount, chargeTimeType, chargeCalculationType);
}
}
}
@@ -1,13 +1,10 @@
package org.mifosng.platform.loan.domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@@ -16,17 +13,20 @@ import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeCalculationType;
import org.mifosng.platform.charge.domain.ChargeTimeType;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@IdClass(LoanCharge.LoanChargePK.class)
@Table(name = "m_loan_charge")
public class LoanCharge {
public class LoanCharge extends AbstractPersistable<Long> {
@Id
@SuppressWarnings("unused")
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable=false)
private Loan loan;
@SuppressWarnings("unused")
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "charge_id", referencedColumnName = "id", nullable=false)
private Charge charge;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
@@ -85,20 +85,6 @@ public class LoanCharge {
this.chargeCalculation = chargeCalculation;
}
public static class LoanChargePK implements Serializable {
@SuppressWarnings("unused")
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable=false)
private Loan loan;
@SuppressWarnings("unused")
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "charge_id", referencedColumnName = "id", nullable=false)
private Charge charge;
}
public void update(final Loan loan) {
this.loan = loan;
}
@@ -20,6 +20,7 @@ import org.mifosng.platform.exceptions.ChargeIsNotActiveException;
import org.mifosng.platform.exceptions.ChargeNotFoundException;
import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.FundNotFoundException;
import org.mifosng.platform.exceptions.InvalidCurrencyException;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.exceptions.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosng.platform.exceptions.StaffNotFoundException;
@@ -101,7 +102,7 @@ public class LoanAssembler {
Staff loanOfficer= findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
// 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());
final Set<LoanCharge> loanCharges = assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loanProduct.getCurrency().getCode());
Loan loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId());
@@ -130,13 +131,14 @@ public class LoanAssembler {
return loan;
}
public Set<LoanCharge> assembleSetOfLoanCharges(LoanChargeCommand[] chargesPassedAtCreation, Set<Charge> chargesInheritedFromProduct) {
public Set<LoanCharge> assembleSetOfLoanCharges(LoanChargeCommand[] chargesPassedAtCreation,
Set<Charge> chargesInheritedFromProduct, String loanCurrencyCode) {
Set<LoanCharge> loanCharges = new HashSet<LoanCharge>();
if (!ObjectUtils.isEmpty(chargesPassedAtCreation)) {
for (LoanChargeCommand loanChargeCommand : chargesPassedAtCreation) {
Long chargeDefinitionId = loanChargeCommand.getId();
Long chargeDefinitionId = loanChargeCommand.getChargeId();
Charge chargeDefinition = this.chargeRepository.findOne(chargeDefinitionId);
if (chargeDefinition == null || chargeDefinition.isDeleted()) {
throw new ChargeNotFoundException(chargeDefinitionId);
@@ -145,7 +147,12 @@ public class LoanAssembler {
if (!chargeDefinition.isActive()) {
throw new ChargeIsNotActiveException(chargeDefinitionId);
}
if (!loanCurrencyCode.equals(chargeDefinition.getCurrencyCode())){
String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage);
}
loanCharges.add(LoanCharge.createNew(chargeDefinition, loanChargeCommand));
}
} else if (chargesPassedAtCreation == null) {
@@ -22,6 +22,7 @@ public class LoanChargeCommandValidator {
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("chargeId").value(command.getChargeId()).notNull().longGreaterThanZero();
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4);
@@ -6,11 +6,11 @@ import java.sql.SQLException;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.ClientData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EnumOptionData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanRepaymentPeriodData;
@@ -403,7 +403,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
lifeCycleStatusDate = closedOnDate;
}
Collection<ChargeData> charges = null;
Collection<LoanChargeData> charges = null;
return new LoanBasicDetailsData(id, externalId, clientId, clientName, clientOfficeId,
loanProductId, loanProductName, loanProductDescription,
fundId, fundName,
@@ -145,7 +145,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Fund fund = this.loanAssembler.findFundByIdIfProvided(command.getFundId());
Staff loanOfficer = this.loanAssembler.findLoanOfficerByIdIfProvided(command.getLoanOfficerId());
LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges()) ;
Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loan.getCurrency().getCode());
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand());
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer);