MIFOSX-53 added post and template methods. WIP.

This commit is contained in:
Michal Dudzinski
2012-09-05 15:46:06 +02:00
parent ca3d666578
commit 99d50b248a
11 changed files with 314 additions and 12 deletions
@@ -0,0 +1,52 @@
package org.mifosng.platform.api.commands;
import java.math.BigDecimal;
import java.util.Set;
public class LoanChargeCommand {
private final Long id;
private final BigDecimal amount;
private final Integer chargeTimeType;
private final Integer chargeCalculationType;
private final Set<String> modifiedParameters;
public LoanChargeCommand(final Set<String> modifiedParameters, final Long id, final BigDecimal amount,
final Integer chargeTimeType, final Integer chargeCalculationType) {
this.modifiedParameters = modifiedParameters;
this.id = id;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
}
public Long getId() {
return id;
}
public BigDecimal getAmount() {
return amount;
}
public Integer getChargeTimeType() {
return chargeTimeType;
}
public Integer getChargeCalculationType() {
return chargeCalculationType;
}
public boolean isAmountChanged(){
return this.modifiedParameters.contains("amount");
}
public boolean isChargeTimeTypeChanged(){
return this.modifiedParameters.contains("chargeTimeType");
}
public boolean isChargeCalculationTypeChanged(){
return this.modifiedParameters.contains("chargeCalculationType");
}
}
@@ -38,7 +38,9 @@ public class SubmitLoanApplicationCommand {
private final LocalDate interestChargedFromDate;
private final LocalDate submittedOnDate;
private final String submittedOnNote;
private final LoanChargeCommand[] charges;
private LoanSchedule loanSchedule;
public SubmitLoanApplicationCommand(
@@ -55,7 +57,7 @@ public class SubmitLoanApplicationCommand {
final Integer interestCalculationPeriodMethod,
final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod,
final Integer loanTermFrequency, final Integer loanTermFrequencyType,
final BigDecimal toleranceAmount) {
final BigDecimal toleranceAmount, final LoanChargeCommand[] charges) {
this.clientId = clientId;
this.productId = productId;
this.externalId = externalId;
@@ -81,6 +83,8 @@ public class SubmitLoanApplicationCommand {
this.repaymentFrequencyType = repaymentFrequency;
this.numberOfRepayments = numberOfRepayments;
this.amortizationType = amortizationMethod;
this.charges = charges;
}
public CalculateLoanScheduleCommand toCalculateLoanScheduleCommand() {
@@ -185,4 +189,9 @@ public class SubmitLoanApplicationCommand {
public String getSubmittedOnNote() {
return submittedOnNote;
}
public LoanChargeCommand[] getCharges() {
return charges;
}
}
@@ -27,6 +27,7 @@ import org.mifosng.platform.api.commands.DepositStateTransitionApprovalCommand;
import org.mifosng.platform.api.commands.DepositStateTransitionCommand;
import org.mifosng.platform.api.commands.FundCommand;
import org.mifosng.platform.api.commands.GroupCommand;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.commands.LoanProductCommand;
import org.mifosng.platform.api.commands.LoanStateTransitionCommand;
import org.mifosng.platform.api.commands.LoanTransactionCommand;
@@ -479,14 +480,49 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
LocalDate submittedOnDate = extractLocalDateParameter("submittedOnDate", requestMap, modifiedParameters);
String submittedOnNote = extractStringParameter("submittedOnNote", requestMap, modifiedParameters);
JsonParser parser = new JsonParser();
LoanChargeCommand[] charges = null;
JsonElement element = parser.parse(json);
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has("charges")) {
modifiedParameters.add("charges");
JsonArray array = object.get("charges").getAsJsonArray();
charges = new LoanChargeCommand[array.size()];
for (int i=0; i<array.size(); i++) {
JsonObject loanCharge = array.get(i).getAsJsonObject();
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 = loanCharge.get("amount").getAsBigDecimal();
}
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);
}
}
}
return new SubmitLoanApplicationCommand(clientId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue,
repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationTypeValue,
loanTermFrequency, loanTermFrequencyType,
inArrearsToleranceValue);
inArrearsToleranceValue, charges);
}
@Override
@@ -1101,6 +1137,4 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
return new DepositStateTransitionApprovalCommand(resourceIdentifier, productId, eventDate, tenureInMonths, depositAmount, interestCompoundedEveryPeriodType,interestCompoundedEvery);
}
}
@@ -73,6 +73,14 @@ public class Charge extends AbstractAuditableCustom<AppUser, Long> {
return currencyCode;
}
public Integer getChargeTime() {
return chargeTime;
}
public Integer getChargeCalculation() {
return chargeCalculation;
}
public boolean isActive() {
return active;
}
@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@@ -128,6 +129,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "maturedon_date")
private Date maturedOnDate;
@SuppressWarnings("unused")
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private Set<LoanCharge> charges;
// see
// http://stackoverflow.com/questions/4334970/hibernate-cannot-simultaneously-fetch-multiple-bags
@LazyCollection(LazyCollectionOption.FALSE)
@@ -160,6 +166,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.client = null;
this.loanProduct = null;
this.loanRepaymentScheduleDetail = null;
this.charges = charges;
}
public Loan(final Client client, Fund fund, LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct,
@@ -924,7 +931,15 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return possibleMaturityDate;
}
public void addRepaymentScheduleInstallment(
public Set<LoanCharge> getCharges() {
return charges;
}
public void setCharges(Set<LoanCharge> charges) {
this.charges = charges;
}
public void addRepaymentScheduleInstallment(
final LoanRepaymentScheduleInstallment installment) {
installment.updateLoan(this);
this.repaymentScheduleInstallments.add(installment);
@@ -0,0 +1,80 @@
package org.mifosng.platform.loan.domain;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.charge.domain.Charge;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
@Entity @IdClass(LoanCharge.LoanChargePK.class)
@Table(name = "m_loan_charge")
public class LoanCharge {
@Id
private Loan loan;
@Id
private Charge charge;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@SuppressWarnings("unused")
@Column(name = "charge_time_enum", nullable = false)
private Integer chargeTime;
@SuppressWarnings("unused")
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
public LoanCharge() {
}
public LoanCharge(Loan loan, Charge charge, LoanChargeCommand command) {
this.loan = loan;
this.charge = charge;
if (command.isAmountChanged()){
this.amount = command.getAmount();
} else {
this.amount = charge.getAmount();
}
if (command.isChargeTimeTypeChanged()){
this.chargeTime = command.getChargeTimeType();
} else {
this.chargeTime = charge.getChargeTime();
}
if (command.isChargeCalculationTypeChanged()){
this.chargeCalculation = command.getChargeCalculationType();
} else {
this.chargeCalculation = charge.getChargeCalculation();
}
}
public LoanCharge(Loan loan, Charge charge) {
this(loan, charge, charge.getAmount(), charge.getChargeTime(), charge.getChargeCalculation());
}
public LoanCharge(Loan loan, Charge charge, BigDecimal amount, Integer chargeTime, Integer chargeCalculation) {
this.loan = loan;
this.charge = charge;
this.amount = amount;
this.chargeTime = chargeTime;
this.chargeCalculation = chargeCalculation;
}
public static class LoanChargePK implements Serializable{
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "loan_id", referencedColumnName = "id", nullable=false)
private Loan loan;
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "charge_id", referencedColumnName = "id", nullable=false)
private Charge charge;
}
}
@@ -153,7 +153,11 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
return transactionProcessingStrategy;
}
public void update(final LoanProductCommand command, final Fund fund, final LoanTransactionProcessingStrategy strategy,
public Set<Charge> getCharges() {
return charges;
}
public void update(final LoanProductCommand command, final Fund fund, final LoanTransactionProcessingStrategy strategy,
final Set<Charge> charges) {
if (command.isNameChanged()) {
@@ -2,17 +2,25 @@ package org.mifosng.platform.loan.service;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeRepository;
import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.client.domain.ClientRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
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.fund.domain.Fund;
@@ -22,6 +30,7 @@ import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanCharge;
import org.mifosng.platform.loan.domain.LoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.LoanProduct;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
@@ -34,6 +43,7 @@ import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.loanschedule.domain.AprCalculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
@Service
public class LoanAssembler {
@@ -42,6 +52,7 @@ public class LoanAssembler {
private final ClientRepository clientRepository;
private final AprCalculator aprCalculator = new AprCalculator();
private final FundRepository fundRepository;
private final ChargeRepository chargeRepository;
private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
@Autowired
@@ -49,10 +60,12 @@ public class LoanAssembler {
final LoanProductRepository loanProductRepository,
final ClientRepository clientRepository,
final FundRepository fundRepository,
final ChargeRepository chargeRepository,
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository) {
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
this.fundRepository = fundRepository;
this.chargeRepository = chargeRepository;
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
}
@@ -96,10 +109,13 @@ public class LoanAssembler {
// associating fund with loan product at creation is optional for now.
Fund fund = findFundByIdIfProvided(command.getFundId());
LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
Loan loan = Loan.createNew(fund, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail);
loan.setExternalId(command.getExternalId());
final Set<LoanCharge> charges = this.assembleSetOfCharges(command, loan, loanProduct, currency.getCode());
loan.setCharges(charges);
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
MoneyData readPrincipalDue = scheduledLoanInstallment.getPrincipalDue();
@@ -111,7 +127,7 @@ public class LoanAssembler {
readInterestDue.getAmount());
loan.addRepaymentScheduleInstallment(installment);
}
loan.submitApplication(loanTermFrequency, loanTermFrequencyType,
command.getSubmittedOnDate(), command.getExpectedDisbursementDate(),
command.getRepaymentsStartingFromDate(), command.getInterestChargedFromDate(),
@@ -146,4 +162,35 @@ public class LoanAssembler {
}
return strategy;
}
private Set<LoanCharge> assembleSetOfCharges(final SubmitLoanApplicationCommand command, final Loan loan,
final LoanProduct product, final String currencyCode) {
Set<LoanCharge> charges = new HashSet<LoanCharge>();
LoanChargeCommand[] loanChargesArray = command.getCharges();
if (!ObjectUtils.isEmpty(loanChargesArray)) {
for (LoanChargeCommand loanChargeCommand : loanChargesArray) {
Long id = loanChargeCommand.getId();
Charge charge = this.chargeRepository.findOne(id);
if (charge == null || charge.isDeleted()) {
throw new ChargeNotFoundException(id);
}
if (!charge.isActive()){
throw new ChargeIsNotActiveException(id);
}
if (!currencyCode.equals(charge.getCurrencyCode())){
String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.loan", errorMessage);
}
charges.add(new LoanCharge(loan, charge, loanChargeCommand));
}
} else {
for (Charge productCharge : product.getCharges()){
charges.add(new LoanCharge(loan, productCharge));
}
}
return charges;
}
}
@@ -0,0 +1,33 @@
package org.mifosng.platform.loan.service;
import org.mifosng.platform.DataValidatorBuilder;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import java.util.ArrayList;
import java.util.List;
public class LoanChargeCommandValidator {
private final LoanChargeCommand command;
public LoanChargeCommandValidator(LoanChargeCommand command) {
this.command = command;
}
public void validate(){
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
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);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}
@@ -7,6 +7,7 @@ import java.util.ArrayList;
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;
@@ -20,6 +21,7 @@ import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.NewLoanData;
import org.mifosng.platform.charge.service.ChargeReadPlatformService;
import org.mifosng.platform.client.service.ClientReadPlatformService;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository;
@@ -54,6 +56,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTransactionRepository loanTransactionRepository;
private final ChargeReadPlatformService chargeReadPlatformService;
@Autowired
public LoanReadPlatformServiceImpl(
@@ -63,6 +66,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final ApplicationCurrencyRepository applicationCurrencyRepository,
final LoanProductReadPlatformService loanProductReadPlatformService,
final ClientReadPlatformService clientReadPlatformService,
final ChargeReadPlatformService chargeReadPlatformService,
final TenantAwareRoutingDataSource dataSource) {
this.context = context;
this.loanRepository = loanRepository;
@@ -70,6 +74,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.clientReadPlatformService = clientReadPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@@ -274,6 +279,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
if (loanProductLookup.getId().equals(productId)) {
match = this.loanProductReadPlatformService
.retrieveLoanProduct(loanProductLookup.getId());
Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges();
chargeOptions.removeAll(match.getCharges());
match.setChargeOptions(chargeOptions);
break;
}
}
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.commands.SubmitLoanApplicationCommand;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
@@ -46,7 +47,18 @@ public class SubmitLoanApplicationCommandValidator {
} catch (PlatformApiDataValidationException e) {
dataValidationErrors.addAll(e.getErrors());
}
if (this.command.getCharges() != null){
for (LoanChargeCommand loanChargeCommand : this.command.getCharges()){
try {
LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanChargeCommand);
validator.validate();
} catch (PlatformApiDataValidationException e) {
dataValidationErrors.addAll(e.getErrors());
}
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}