remove organisation association from client to allow use of JPA and respositories to work for clients. support associating fund with loan account.

This commit is contained in:
Keith Woodlock
2012-07-11 11:46:37 +01:00
parent 7c125b6012
commit b653fd5240
14 changed files with 118 additions and 43 deletions
@@ -0,0 +1,11 @@
-- example of dropping fund column/index/foreign key
-- alter table `mifosngprovider`.`portfolio_loan`
-- DROP FOREIGN KEY `FK7C885877240145`,
-- DROP INDEX `FK7C885877240145`,
-- drop column `fund_id`;
-- associate fund table with loans making fund optional at database level
alter table `mifosngprovider`.`portfolio_loan`
add column `fund_id` bigint(20) DEFAULT NULL,
add KEY `FK7C885877240145` (`fund_id`),
add CONSTRAINT `FK7C885877240145` FOREIGN KEY (`fund_id`) REFERENCES `org_fund` (`id`);
@@ -104,6 +104,7 @@ public final class Specifications {
};
}
@Deprecated
public static Specification<Client> clientsThatMatch(final Organisation organisation, final Long id) {
return new Specification<Client>() {
@@ -105,7 +105,7 @@ public class LoansApiResource {
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON })
public Response calculateLoanSchedule(
public Response calculateLoanScheduleOrSubmitLoanApplication(
@QueryParam("command") final String commandParam,
final SubmitLoanApplicationCommand command) {
@@ -14,6 +14,8 @@ public class SubmitLoanApplicationCommand {
private Long productId;
// product
private Long fundId;
private String locale;
private BigDecimal principalValue;
private String principal;
@@ -334,4 +336,12 @@ public class SubmitLoanApplicationCommand {
public void setInterestChargedFromDate(String interestChargedFromDate) {
this.interestChargedFromDate = interestChargedFromDate;
}
public Long getFundId() {
return fundId;
}
public void setFundId(Long fundId) {
this.fundId = fundId;
}
}
@@ -2,9 +2,6 @@ package org.mifosng.platform.api.data;
import java.io.Serializable;
import org.codehaus.jackson.map.annotate.JsonFilter;
@JsonFilter("myFilter")
public class FundData implements Serializable {
private Long id;
@@ -11,6 +11,7 @@ public class LoanAccountData {
private Long id;
private String externalId;
private FundData fund;
private String loanProductName;
private LocalDate submittedOnDate;
@@ -47,7 +48,6 @@ public class LoanAccountData {
private LoanConvenienceData convenienceData;
protected LoanAccountData() {
//
}
@@ -74,6 +74,7 @@ public class LoanAccountData {
this.id = basicDetails.getId();
this.externalId = basicDetails.getExternalId();
this.fund = basicDetails.getFund();
this.loanProductName = basicDetails.getLoanProductName();
this.submittedOnDate = basicDetails.getSubmittedOnDate();
this.approvedOnDate = basicDetails.getApprovedOnDate();
@@ -153,6 +154,14 @@ public class LoanAccountData {
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public FundData getFund() {
return fund;
}
public void setFund(FundData fund) {
this.fund = fund;
}
public String getLoanProductName() {
return loanProductName;
@@ -11,6 +11,7 @@ public class LoanBasicDetailsData {
private Long id;
private String externalId;
private FundData fund;
private String loanProductName;
private LocalDate submittedOnDate;
@@ -44,7 +45,7 @@ public class LoanBasicDetailsData {
}
public LoanBasicDetailsData(Long id, String externalId,
String loanProductName, LocalDate closedOnDate,
String loanProductName, FundData fund, LocalDate closedOnDate,
LocalDate submittedOnDate, LocalDate approvedOnDate,
LocalDate expectedDisbursedOnLocalDate,
LocalDate acutalDisbursedOnLocalDate,
@@ -59,6 +60,7 @@ public class LoanBasicDetailsData {
this.id = id;
this.externalId = externalId;
this.loanProductName = loanProductName;
this.fund = fund;
this.closedOnDate = closedOnDate;
this.submittedOnDate = submittedOnDate;
this.approvedOnDate = approvedOnDate;
@@ -181,6 +183,14 @@ public class LoanBasicDetailsData {
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public FundData getFund() {
return fund;
}
public void setFund(FundData fund) {
this.fund = fund;
}
public String getLoanProductName() {
return loanProductName;
@@ -9,25 +9,18 @@ import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.ClientCommand;
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
import org.mifosng.platform.organisation.domain.Office;
import org.mifosng.platform.organisation.domain.Organisation;
import org.mifosng.platform.user.domain.AppUser;
@Entity
@Table(name = "portfolio_client", uniqueConstraints = @UniqueConstraint(columnNames = {"org_id", "external_id" }))
@Table(name = "portfolio_client")
public class Client extends AbstractAuditableCustom<AppUser, Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "org_id", nullable = false)
private final Organisation organisation;
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
@@ -46,18 +39,14 @@ public class Client extends AbstractAuditableCustom<AppUser, Long> {
@Temporal(TemporalType.DATE)
private Date joiningDate;
@Column(name = "external_id", length=100)
@Column(name = "external_id", length=100, unique=true)
private String externalId;
public static Client newClient(Organisation organisation,
Office clientOffice, String firstname, String lastname,
LocalDate joiningDate, String externalId) {
return new Client(organisation, clientOffice, firstname, lastname,
joiningDate, externalId);
public static Client newClient(Office clientOffice, String firstname, String lastname, LocalDate joiningDate, String externalId) {
return new Client(clientOffice, firstname, lastname, joiningDate, externalId);
}
public Client() {
this.organisation = null;
this.office = null;
this.joiningDate = null;
this.firstName = null;
@@ -65,9 +54,7 @@ public class Client extends AbstractAuditableCustom<AppUser, Long> {
this.externalId = null;
}
public Client(final Organisation organisation, final Office office, final String firstName,
final String lastName, final LocalDate openingDate, final String externalId) {
this.organisation = organisation;
private Client(final Office office, final String firstName, final String lastName, final LocalDate openingDate, final String externalId) {
this.office = office;
if (StringUtils.isNotBlank(externalId)) {
this.externalId = externalId.trim();
@@ -1,6 +1,5 @@
package org.mifosng.platform.client.service;
import static org.mifosng.platform.Specifications.clientsThatMatch;
import static org.mifosng.platform.Specifications.notesThatMatch;
import static org.mifosng.platform.Specifications.officesThatMatch;
@@ -61,7 +60,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
firstname = null;
}
Client newClient = Client.newClient(currentUser.getOrganisation(), clientOffice, firstname, lastname, command.getJoiningLocalDate(), command.getExternalId());
Client newClient = Client.newClient(clientOffice, firstname, lastname, command.getJoiningLocalDate(), command.getExternalId());
this.clientRepository.save(newClient);
@@ -93,7 +92,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
firstname = null;
}
Client clientForUpdate = this.clientRepository.findOne(clientsThatMatch(currentUser.getOrganisation(), command.getId()));
// Client clientForUpdate = this.clientRepository.findOne(clientsThatMatch(currentUser.getOrganisation(), command.getId()));
Client clientForUpdate = this.clientRepository.findOne(command.getId());
if (clientForUpdate == null) {
throw new ClientNotFoundException(command.getId());
}
@@ -110,7 +110,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
AppUser currentUser = context.authenticatedUser();
Client clientForUpdate = this.clientRepository.findOne(clientsThatMatch(currentUser.getOrganisation(), command.getClientId()));
// Client clientForUpdate = this.clientRepository.findOne(clientsThatMatch(currentUser.getOrganisation(), command.getClientId()));
Client clientForUpdate = this.clientRepository.findOne(command.getClientId());
if (clientForUpdate == null) {
throw new ClientNotFoundException(command.getClientId());
}
@@ -28,6 +28,7 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DerivedLoanData;
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.LoanAccountSummaryData;
import org.mifosng.platform.api.data.LoanBasicDetailsData;
@@ -40,6 +41,7 @@ import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.InvalidLoanStateTransitionException;
import org.mifosng.platform.exceptions.InvalidLoanTransactionTypeException;
import org.mifosng.platform.fund.domain.Fund;
import org.mifosng.platform.infrastructure.AbstractAuditableCustom;
import org.mifosng.platform.loanproduct.service.LoanEnumerations;
import org.mifosng.platform.organisation.domain.Organisation;
@@ -60,6 +62,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne
@JoinColumn(name = "product_id")
private final LoanProduct loanProduct;
@ManyToOne
@JoinColumn(name = "fund_id", nullable = true)
private Fund fund;
@Column(name = "external_id")
private String externalId;
@@ -144,9 +150,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private final InterestRebateCalculatorFactory interestRebateCalculatorFactory = new DailyEquivalentInterestRebateCalculatorFactory();
public static Loan createNew(Organisation organisation,
LoanProduct loanProduct, Client client,
Fund fund, LoanProduct loanProduct, Client client,
LoanProductRelatedDetail loanRepaymentScheduleDetail) {
return new Loan(organisation, client, loanProduct, loanRepaymentScheduleDetail, null);
return new Loan(organisation, client, fund, loanProduct, loanRepaymentScheduleDetail, null);
}
public Loan() {
@@ -157,11 +163,16 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
}
public Loan(final Organisation organisation, final Client client,
final LoanProduct loanProduct,
Fund fund, final LoanProduct loanProduct,
final LoanProductRelatedDetail loanRepaymentScheduleDetail,
final LoanStatus loanStatus) {
this.organisation = organisation;
this.client = client;
if (fund != null) {
this.fund = fund;
} else {
this.fund = loanProduct.getFund();
}
this.loanProduct = loanProduct;
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanStatus = loanStatus;
@@ -176,6 +187,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return this.client;
}
public Fund getFund() {
return this.fund;
}
public LoanProduct getLoanProduct() {
return this.loanProduct;
}
@@ -968,7 +983,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return statusSinceDate;
}
public LoanBasicDetailsData toBasicDetailsData(CurrencyData currencyData) {
public LoanBasicDetailsData toBasicDetailsData(final CurrencyData currencyData, final FundData fundData) {
Money loanPrincipal = this.loanRepaymentScheduleDetail.getPrincipal();
MoneyData principal = MoneyData.of(currencyData, loanPrincipal.getAmount());
@@ -982,7 +997,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
EnumOptionData interestType = LoanEnumerations.interestType(this.loanRepaymentScheduleDetail.getInterestMethod());
EnumOptionData interestCalculationPeriodType = LoanEnumerations.interestCalculationPeriodType(this.loanRepaymentScheduleDetail.getInterestCalculationPeriodMethod());
return new LoanBasicDetailsData(getId(), this.externalId, this.loanProduct.getName(),
return new LoanBasicDetailsData(getId(), this.externalId, this.loanProduct.getName(), fundData,
getClosedOnDate(), getSubmittedOnDate(), getApprovedOnDate(), getExpectedDisbursedOnLocalDate(), getDisbursedOnDate(),
getExpectedMaturityDate(), getExpectedFirstRepaymentOnDate(), getInterestChargedFromDate(), principal, tolerance,
this.loanRepaymentScheduleDetail.getNumberOfRepayments(), this.loanRepaymentScheduleDetail.getRepayEvery(),
@@ -996,9 +1011,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
public LoanAccountData toLoanAccountData(LoanAccountSummaryData summary, LoanRepaymentScheduleData repaymentSchedule,
List<LoanTransactionData> loanRepayments,
CurrencyData currencyData) {
CurrencyData currencyData, FundData fundData) {
LoanBasicDetailsData basicDetails = toBasicDetailsData(currencyData);
LoanBasicDetailsData basicDetails = toBasicDetailsData(currencyData, fundData);
// permissions
boolean waiveAllowed = summary.isWaiveAllowed(basicDetails.getInArrearsTolerance())
@@ -139,6 +139,10 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
return this.loanProductRelatedDetail.getAmortizationMethod();
}
public Fund getFund() {
return fund;
}
public void update(final LoanProductCommand command, final Fund fund) {
if (command.getName() != null) {
this.name = command.getName();
@@ -1,6 +1,6 @@
package org.mifosng.platform.loan.service;
import static org.mifosng.platform.Specifications.clientsThatMatch;
import static org.mifosng.platform.Specifications.fundsThatMatch;
import static org.mifosng.platform.Specifications.productThatMatches;
import java.math.BigDecimal;
@@ -14,7 +14,10 @@ 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.ClientNotFoundException;
import org.mifosng.platform.exceptions.FundNotFoundException;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.fund.domain.Fund;
import org.mifosng.platform.fund.domain.FundRepository;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
@@ -40,14 +43,16 @@ public class LoanAssembler {
private final LoanProductRepository loanProductRepository;
private final ClientRepository clientRepository;
private final AprCalculator aprCalculator = new AprCalculator();
private final FundRepository fundRepository;
@Autowired
public LoanAssembler(final LoanStatusRepository loanStatusRepository,
final LoanProductRepository loanProductRepository,
final ClientRepository clientRepository) {
final ClientRepository clientRepository, final FundRepository fundRepository) {
this.loanStatusRepository = loanStatusRepository;
this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository;
this.fundRepository = fundRepository;
}
public Loan assembleFrom(SubmitLoanApplicationCommand command, Organisation organisation) {
@@ -55,7 +60,8 @@ public class LoanAssembler {
if (loanProduct == null) {
throw new LoanProductNotFoundException(command.getProductId());
}
Client client = this.clientRepository.findOne(clientsThatMatch(organisation, command.getClientId()));
// Client client = this.clientRepository.findOne(clientsThatMatch(organisation, command.getClientId()));
Client client = this.clientRepository.findOne(command.getClientId());
if (client == null) {
throw new ClientNotFoundException(command.getClientId());
}
@@ -81,8 +87,11 @@ public class LoanAssembler {
LoanSchedule loanSchedule = command.getLoanSchedule();
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
// associating fund with loan product at creation is optional for now.
Fund fund = findFundByIdIfProvided(organisation, command.getFundId());
Loan loan = Loan.createNew(organisation, loanProduct, client, loanRepaymentScheduleDetail);
Loan loan = Loan.createNew(organisation, fund, loanProduct, client, loanRepaymentScheduleDetail);
loan.setExternalId(command.getExternalId());
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
@@ -107,4 +116,15 @@ public class LoanAssembler {
List<LoanStatus> allowedLoanStatuses = this.loanStatusRepository.findAll();
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
}
private Fund findFundByIdIfProvided(final Organisation organisation, final Long fundId) {
Fund fund = null;
if (fundId != null) {
fund = this.fundRepository.findOne(fundsThatMatch(organisation, fundId));
if (fund == null) {
throw new FundNotFoundException(fundId);
}
}
return fund;
}
}
@@ -10,6 +10,7 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.ClientData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.DerivedLoanData;
import org.mifosng.platform.api.data.FundData;
import org.mifosng.platform.api.data.LoanAccountData;
import org.mifosng.platform.api.data.LoanProductData;
import org.mifosng.platform.api.data.LoanProductLookup;
@@ -23,6 +24,7 @@ import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.CurrencyNotFoundException;
import org.mifosng.platform.exceptions.LoanNotFoundException;
import org.mifosng.platform.exceptions.LoanTransactionNotFoundException;
import org.mifosng.platform.fund.service.FundReadPlatformService;
import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanRepository;
import org.mifosng.platform.loan.domain.LoanTransaction;
@@ -44,6 +46,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final LoanProductReadPlatformService loanProductReadPlatformService;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTransactionRepository loanTransactionRepository;
private final FundReadPlatformService fundReadPlatformService;
@Autowired
public LoanReadPlatformServiceImpl(
@@ -52,12 +55,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionRepository loanTransactionRepository,
final ApplicationCurrencyRepository applicationCurrencyRepository,
final LoanProductReadPlatformService loanProductReadPlatformService,
final FundReadPlatformService fundReadPlatformService,
final ClientReadPlatformService clientReadPlatformService) {
this.context = context;
this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.loanProductReadPlatformService = loanProductReadPlatformService;
this.fundReadPlatformService = fundReadPlatformService;
this.clientReadPlatformService = clientReadPlatformService;
}
@@ -72,7 +77,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
if (loan == null) {
throw new LoanNotFoundException(loanId);
}
FundData fundData = null;
if (loan.getFund() != null) {
fundData = this.fundReadPlatformService.retrieveFund(loan.getFund().getId());
}
final String currencyCode = loan.getCurrencyCode();
ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(currencyCode);
if (currency == null) {
@@ -83,14 +93,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
currency.getName(), currency.getDecimalPlaces(),
currency.getDisplaySymbol(), currency.getNameCode());
return convertToData(loan, currencyData);
return convertToData(loan, currencyData, fundData);
}
private LoanAccountData convertToData(final Loan realLoan, CurrencyData currencyData) {
private LoanAccountData convertToData(final Loan realLoan, CurrencyData currencyData, FundData fundData) {
DerivedLoanData loanData = realLoan.deriveLoanData(currencyData);
return realLoan.toLoanAccountData(loanData.getSummary(), loanData.getRepaymentSchedule(), loanData.getLoanRepayments(), currencyData);
return realLoan.toLoanAccountData(loanData.getSummary(), loanData.getRepaymentSchedule(), loanData.getLoanRepayments(), currencyData, fundData);
}
@Override