diff --git a/mifosng-db/patches/patch-09-add-account-identifier-to-loan.sql b/mifosng-db/patches/patch-09-add-account-identifier-to-loan.sql new file mode 100644 index 000000000..6b5bc1c87 --- /dev/null +++ b/mifosng-db/patches/patch-09-add-account-identifier-to-loan.sql @@ -0,0 +1,13 @@ +ALTER TABLE `m_loan` +ADD COLUMN `account_no` VARCHAR(20) NOT NULL AFTER `id`; + +UPDATE `m_loan` +SET +`account_no` = `id` +WHERE `id` > 0; + +ALTER TABLE `m_loan` ADD UNIQUE INDEX `loan_account_no_UNIQUE` (`account_no` ASC); + + +ALTER TABLE `m_loan` DROP INDEX `org_id`; +ALTER TABLE `m_loan` ADD UNIQUE INDEX `loan_externalid_UNIQUE` (`external_id` ASC); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java index 4302db962..1fb897811 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java @@ -265,7 +265,7 @@ public class ClientsApiResource { @Path("{clientId}/loans") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String retrieveClientAccount(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo) { + public String retrieveClientAccounts(@PathParam("clientId") final Long clientId, @Context final UriInfo uriInfo) { context.authenticatedUser().validateHasReadPermission("CLIENT"); @@ -278,7 +278,6 @@ public class ClientsApiResource { "withdrawnByClientDepositAccountsCount", "withdrawnByClientDepositAccounts", "closedDepositAccountsCount", "closedDepositAccounts", "maturedDepositAccountsCount", "maturedDepositAccounts", "rejectedDepositAccountsCount", "rejectedDepositAccounts", "preclosedDepositAccountsCount", "preclosedDepositAccounts", - "pendingApprovalSavingAccountsCount", "pendingApprovalSavingAccounts", "approvedSavingAccountsCount", "approvedSavingAccounts", "withdrawnByClientSavingAccountsCount", "withdrawnByClientSavingAccounts", "rejectedSavingAccountsCount", "rejectedSavingAccounts", "closedSavingAccountsCount", "closedSavingAccounts")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryData.java index 36e9929a7..757df41a4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientAccountSummaryData.java @@ -10,6 +10,8 @@ public class ClientAccountSummaryData { @SuppressWarnings("unused") private final Long id; @SuppressWarnings("unused") + private final String accountNo; + @SuppressWarnings("unused") private final String externalId; @SuppressWarnings("unused") private final Long productId; @@ -22,6 +24,7 @@ public class ClientAccountSummaryData { public ClientAccountSummaryData(final Long id, final String externalId, final Long productId, final String loanProductName, final Integer loanStatusId) { this.id = id; + this.accountNo = null; this.externalId = externalId; this.productId = productId; this.productName = loanProductName; @@ -29,13 +32,10 @@ public class ClientAccountSummaryData { this.status = null; } - /** - * Rather than use id, just be consistent and use EnumOptionData for the - * status of an account. - */ - public ClientAccountSummaryData(final Long id, final String externalId, final Long productId, final String loanProductName, + public ClientAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId, final String loanProductName, final EnumOptionData loanStatus) { this.id = id; + this.accountNo = accountNo; this.externalId = externalId; this.productId = productId; this.productName = loanProductName; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountIdentifierGeneratorFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountIdentifierGeneratorFactory.java deleted file mode 100644 index c3f4c25a1..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountIdentifierGeneratorFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.mifosplatform.portfolio.client.domain; - -import org.springframework.stereotype.Component; - -@Component -public class AccountIdentifierGeneratorFactory { - - public AccountNumberGenerator determineClientAccountNoGenerator(final Long clientId) { - return new ClientAccountNumberGenerator(clientId, 9); - } - -} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGenerator.java index 614cf3402..70ebad6bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGenerator.java @@ -3,7 +3,7 @@ package org.mifosplatform.portfolio.client.domain; /** * Responsible for generating unique account number based on some rules or patterns. * - * @see ClientAccountNumberGenerator + * @see ZeroPaddedAccountNumberGenerator */ public interface AccountNumberGenerator { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGeneratorFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGeneratorFactory.java new file mode 100644 index 000000000..daffc56e2 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/AccountNumberGeneratorFactory.java @@ -0,0 +1,15 @@ +package org.mifosplatform.portfolio.client.domain; + +import org.springframework.stereotype.Component; + +@Component +public class AccountNumberGeneratorFactory { + + public AccountNumberGenerator determineClientAccountNoGenerator(final Long clientId) { + return new ZeroPaddedAccountNumberGenerator(clientId, 9); + } + + public AccountNumberGenerator determineLoanAccountNoGenerator(final Long loanId) { + return new ZeroPaddedAccountNumberGenerator(loanId, 9); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java index 5e8ef6fbd..6b0ad9cd5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java @@ -29,7 +29,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_client") public class Client extends AbstractPersistable { - @Column(name = "account_no", length = 40, unique = true, nullable = false) + @Column(name = "account_no", length = 20, unique = true, nullable = false) private String accountNumber; @ManyToOne @@ -148,8 +148,9 @@ public class Client extends AbstractPersistable { this.office = newOffice; } - public void updateAccountIdentifier(final String accountIdentifier) { + public void updateAccountNo(final String accountIdentifier) { this.accountNumber = accountIdentifier; + this.accountNumberRequiresAutoGeneration = false; } public Map update(final JsonCommand command) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientAccountNumberGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientAccountNumberGenerator.java deleted file mode 100644 index 7c57274f6..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ClientAccountNumberGenerator.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.mifosplatform.portfolio.client.domain; - -/** - * Example {@link AccountNumberGenerator} for clients that takes clients - * auto generated database id and zero fills it ensuring the identifier is - * always of a given maxLength. - */ -public class ClientAccountNumberGenerator implements AccountNumberGenerator { - - private final Long clientId; - private final int maxLength; - - public ClientAccountNumberGenerator(final Long clientId, int maxLength) { - this.clientId = clientId; - this.maxLength = maxLength;} - - @Override - public String generate() { - return String.format("%0" + maxLength + "d", clientId); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ZeroPaddedAccountNumberGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ZeroPaddedAccountNumberGenerator.java new file mode 100644 index 000000000..b06f51a40 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/ZeroPaddedAccountNumberGenerator.java @@ -0,0 +1,22 @@ +package org.mifosplatform.portfolio.client.domain; + +/** + * Example {@link AccountNumberGenerator} for clients that takes an entities + * auto generated database id and zero fills it ensuring the identifier is + * always of a given maxLength. + */ +public class ZeroPaddedAccountNumberGenerator implements AccountNumberGenerator { + + private final Long id; + private final int maxLength; + + public ZeroPaddedAccountNumberGenerator(final Long id, int maxLength) { + this.id = id; + this.maxLength = maxLength; + } + + @Override + public String generate() { + return String.format("%0" + maxLength + "d", id); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java index c15f98263..1691d351e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -308,7 +308,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService public String loanAccountSummarySchema() { - StringBuilder accountsSummary = new StringBuilder("l.id as id, l.external_id as externalId,"); + StringBuilder accountsSummary = new StringBuilder("l.id as id, l.account_no as accountNo, l.external_id as externalId,"); accountsSummary.append("l.product_id as productId, lp.name as productName,").append("l.loan_status_id as statusId ") .append("from m_loan l ").append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id "); @@ -319,13 +319,14 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService public ClientAccountSummaryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { final Long id = JdbcSupport.getLong(rs, "id"); + final String accountNo = rs.getString("accountNo"); final String externalId = rs.getString("externalId"); final Long productId = JdbcSupport.getLong(rs, "productId"); final String loanProductName = rs.getString("productName"); final Integer loanStatusId = JdbcSupport.getInteger(rs, "statusId"); final EnumOptionData loanStatus = LoanEnumerations.status(loanStatusId); - return new ClientAccountSummaryData(id, externalId, productId, loanProductName, loanStatus); + return new ClientAccountSummaryData(id, accountNo, externalId, productId, loanProductName, loanStatus); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index ae6b91b3f..87fc68d19 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -22,7 +22,7 @@ import org.mifosplatform.organisation.office.domain.OfficeRepository; import org.mifosplatform.organisation.office.exception.OfficeNotFoundException; import org.mifosplatform.portfolio.client.command.ClientIdentifierCommand; import org.mifosplatform.portfolio.client.command.ClientNoteCommand; -import org.mifosplatform.portfolio.client.domain.AccountIdentifierGeneratorFactory; +import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientIdentifier; @@ -58,7 +58,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP private final ClientCommandFromApiJsonDeserializer fromApiJsonDeserializer; private final ClientNoteCommandFromApiJsonDeserializer clientNoteFromApiJsonDeserializer; private final ClientIdentifierCommandFromApiJsonDeserializer clientIdentifierCommandFromApiJsonDeserializer; - private final AccountIdentifierGeneratorFactory accountIdentifierGeneratorFactory; + private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; @Autowired public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final ClientRepository clientRepository, @@ -67,7 +67,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final ClientCommandFromApiJsonDeserializer fromApiJsonDeserializer, final ClientNoteCommandFromApiJsonDeserializer clientNoteFromApiJsonDeserializer, final ClientIdentifierCommandFromApiJsonDeserializer clientIdentifierCommandFromApiJsonDeserializer, - final AccountIdentifierGeneratorFactory accountIdentifierGeneratorFactory) { + final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory) { this.context = context; this.clientRepository = clientRepository; this.clientIdentifierRepository = clientIdentifierRepository; @@ -123,8 +123,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateForCreate(command.json()); - final String officeIdParamName = "officeId"; - final Long officeId = command.longValueOfParameterNamed(officeIdParamName); + final Long officeId = command.longValueOfParameterNamed("officeId"); final Office clientOffice = this.officeRepository.findOne(officeId); if (clientOffice == null) { throw new OfficeNotFoundException(officeId); } @@ -135,7 +134,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP if (newClient.isAccountNumberRequiresAutoGeneration()) { final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory .determineClientAccountNoGenerator(newClient.getId()); - newClient.updateAccountIdentifier(accountNoGenerator.generate()); + newClient.updateAccountNo(accountNoGenerator.generate()); this.clientRepository.save(newClient); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 2439d713b..1bba0507f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -69,14 +69,14 @@ import com.google.gson.JsonElement; @Scope("singleton") public class LoansApiResource { - private final Set LOAN_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "externalId", "clientId", "groupId", - "clientName", "groupName", "fundId", "fundName", "loanProductId", "loanProductName", "loanProductDescription", "currency", - "principal", "inArrearsTolerance", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod", "annualInterestRate", - "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", - "submittedOnDate", "approvedOnDate", "expectedDisbursementDate", "actualDisbursementDate", "expectedFirstRepaymentOnDate", - "interestChargedFromDate", "closedOnDate", "expectedMaturityDate", "status", "lifeCycleStatusDate", "repaymentSchedule", - "transactions", "permissions", "convenienceData", "charges", "productOptions", "amortizationTypeOptions", - "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions", + private final Set LOAN_DATA_PARAMETERS = new HashSet(Arrays.asList("id", "accountNo", "externalId", "clientId", + "groupId", "clientName", "groupName", "fundId", "fundName", "loanProductId", "loanProductName", "loanProductDescription", + "currency", "principal", "inArrearsTolerance", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod", + "annualInterestRate", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", + "interestCalculationPeriodType", "submittedOnDate", "approvedOnDate", "expectedDisbursementDate", "actualDisbursementDate", + "expectedFirstRepaymentOnDate", "interestChargedFromDate", "closedOnDate", "expectedMaturityDate", "status", + "lifeCycleStatusDate", "repaymentSchedule", "transactions", "permissions", "convenienceData", "charges", "productOptions", + "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerId", "loanOfficerName", "loanOfficerOptions", "chargeTemplate")); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java deleted file mode 100644 index 20f709636..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/command/LoanApplicationCommand.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.command; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; -import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; - -/** - * Immutable command for submitting new loan application. - */ -public class LoanApplicationCommand { - - private final Long clientId; - private final Long groupId; - private final Long productId; - @SuppressWarnings("unused") - private final String externalId; - - @SuppressWarnings("unused") - private final Long fundId; - @SuppressWarnings("unused") - private final Long loanOfficerId; - private final Long transactionProcessingStrategyId; - - private final BigDecimal principal; - @SuppressWarnings("unused") - private final BigDecimal inArrearsTolerance; - - private final Integer loanTermFrequency; - private final Integer loanTermFrequencyType; - - private final Integer numberOfRepayments; - private final Integer repaymentEvery; - - private final BigDecimal interestRatePerPeriod; - private final Integer repaymentFrequencyType; - private final Integer interestRateFrequencyType; - private final Integer amortizationType; - private final Integer interestType; - private final Integer interestCalculationPeriodType; - - private final LocalDate expectedDisbursementDate; - private final LocalDate repaymentsStartingFromDate; - private final LocalDate interestChargedFromDate; - private final LocalDate submittedOnDate; - @SuppressWarnings("unused") - private final String submittedOnNote; - - private final LoanChargeCommand[] charges; - - public LoanApplicationCommand(final Long clientId, final Long groupId, final Long productId, final String externalId, - final Long fundId, final Long transactionProcessingStrategyId, final LocalDate submittedOnDate, final String submittedOnNote, - final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, - final LocalDate interestChargedFromLocalDate, final BigDecimal principal, final BigDecimal interestRatePerPeriod, - final Integer interestRateFrequencyMethod, final Integer interestMethod, final Integer interestCalculationPeriodMethod, - final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod, - final Integer loanTermFrequency, final Integer loanTermFrequencyType, final BigDecimal toleranceAmount, - final LoanChargeCommand[] charges, final Long loanOfficerId) { - this.clientId = clientId; - this.groupId = groupId; - this.productId = productId; - this.externalId = externalId; - this.fundId = fundId; - this.loanOfficerId = loanOfficerId; - this.transactionProcessingStrategyId = transactionProcessingStrategyId; - - this.submittedOnDate = submittedOnDate; - this.submittedOnNote = submittedOnNote; - this.expectedDisbursementDate = expectedDisbursementDate; - this.repaymentsStartingFromDate = repaymentsStartingFromDate; - this.interestChargedFromDate = interestChargedFromLocalDate; - - this.principal = principal; - this.loanTermFrequency = loanTermFrequency; - this.loanTermFrequencyType = loanTermFrequencyType; - this.inArrearsTolerance = toleranceAmount; - - this.interestRatePerPeriod = interestRatePerPeriod; - this.interestRateFrequencyType = interestRateFrequencyMethod; - this.interestType = interestMethod; - this.interestCalculationPeriodType = interestCalculationPeriodMethod; - this.repaymentEvery = repaymentEvery; - this.repaymentFrequencyType = repaymentFrequency; - this.numberOfRepayments = numberOfRepayments; - this.amortizationType = amortizationMethod; - - this.charges = charges; - } - - public CalculateLoanScheduleQuery toCalculateLoanScheduleCommand() { - return new CalculateLoanScheduleQuery(this.productId, this.principal, this.interestRatePerPeriod, this.interestRateFrequencyType, - this.interestType, this.interestCalculationPeriodType, this.repaymentEvery, this.repaymentFrequencyType, - this.numberOfRepayments, this.amortizationType, this.loanTermFrequency, this.loanTermFrequencyType, - this.expectedDisbursementDate, this.repaymentsStartingFromDate, this.interestChargedFromDate, this.charges); - } - - public LoanChargeCommand[] getCharges() { - return charges; - } - - public List validate() { - final List dataValidationErrors = new ArrayList(); - - DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - - if (this.clientId != null) { - baseDataValidator.reset().parameter("clientId").value(this.clientId).longGreaterThanZero(); - } - if (this.groupId != null) { - baseDataValidator.reset().parameter("groupId").value(this.groupId).longGreaterThanZero(); - } - - if (this.clientId == null && this.groupId == null) { - baseDataValidator.reset().parameter("clientId").value(this.clientId).notNull().integerGreaterThanZero(); - } - - baseDataValidator.reset().parameter("productId").value(this.productId).notNull().integerGreaterThanZero(); - - if (this.submittedOnDate == null) { - ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.blank", - "The parameter submittedOnDate cannot be empty.", "submittedOnDate"); - dataValidationErrors.add(error); - } else { - if (this.submittedOnDate.isAfter(this.expectedDisbursementDate)) { - ApiParameterError error = ApiParameterError.parameterError( - "validation.msg.loan.submitted.on.date.cannot.be.after.expectedDisbursementDate", - "The date of parameter submittedOnDate cannot fall after the date given for expectedDisbursementDate.", - "submittedOnDate", this.submittedOnDate, this.expectedDisbursementDate); - dataValidationErrors.add(error); - } - } - - if (this.transactionProcessingStrategyId == null) { - baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(this.transactionProcessingStrategyId).notNull() - .inMinMaxRange(2, 2); - } - - if (this.charges != null) { - for (LoanChargeCommand loanChargeCommand : this.charges) { - try { - loanChargeCommand.validateForCreate(); - } catch (PlatformApiDataValidationException e) { - dataValidationErrors.addAll(e.getErrors()); - } - } - } - - return dataValidationErrors; - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index cad1edbdd..4ea49aa02 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -22,6 +22,7 @@ public class LoanAccountData { // basic loan details private final Long id; + private final String accountNo; private final String externalId; private final Long clientId; private final String clientName; @@ -129,6 +130,7 @@ public class LoanAccountData { } this.id = basicDetails.getId(); + this.accountNo = basicDetails.getAccountNo(); this.externalId = basicDetails.getExternalId(); this.clientId = basicDetails.getClientId(); this.clientName = basicDetails.getClientName(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanBasicDetailsData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanBasicDetailsData.java index 55394ea59..3e66f5f7a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanBasicDetailsData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanBasicDetailsData.java @@ -20,6 +20,7 @@ import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; public class LoanBasicDetailsData { private final Long id; + private final String accountNo; private final String externalId; private final Long clientId; private final Long clientOfficeId; @@ -105,6 +106,7 @@ public class LoanBasicDetailsData { final EnumOptionData interestCalculationPeriodType, final LocalDate expectedDisbursementDate, final Collection charges) { this.id = null; + this.accountNo = null; this.externalId = null; this.clientId = clientId; this.clientName = clientName; @@ -163,6 +165,7 @@ public class LoanBasicDetailsData { private LoanBasicDetailsData(final Long clientId, final String clientName, final Long clientOfficeId, final Long groupId, final String groupName, final Long groupOfficeId, final LocalDate expectedDisbursementDate) { this.id = null; + this.accountNo = null; this.externalId = null; this.clientId = clientId; this.clientName = clientName; @@ -210,10 +213,10 @@ public class LoanBasicDetailsData { this.totalDisbursementCharges = BigDecimal.ZERO; } - public LoanBasicDetailsData(final Long id, final String externalId, final Long clientId, final String clientName, - final Long clientOfficeId, final Long groupId, final String groupName, final Long groupOfficeId, final Long loanProductId, - final String loanProductName, final String loanProductDescription, final Long fundId, String fundName, - final LocalDate closedOnDate, final LocalDate submittedOnDate, final LocalDate approvedOnDate, + public LoanBasicDetailsData(final Long id, final String accountNo, final String externalId, final Long clientId, + final String clientName, final Long clientOfficeId, final Long groupId, final String groupName, final Long groupOfficeId, + final Long loanProductId, final String loanProductName, final String loanProductDescription, final Long fundId, + String fundName, final LocalDate closedOnDate, final LocalDate submittedOnDate, final LocalDate approvedOnDate, final LocalDate expectedDisbursementDate, final LocalDate actualDisbursementDate, final LocalDate expectedMaturityDate, final LocalDate repaymentsStartingFromDate, final LocalDate interestChargedFromDate, final CurrencyData currency, final BigDecimal principal, final BigDecimal inArrearsTolerance, final Integer numberOfRepayments, @@ -224,6 +227,7 @@ public class LoanBasicDetailsData { final EnumOptionData termPeriodFrequencyType, final Integer transactionStrategyId, final Collection charges, final Long loanOfficerId, String loanOfficerName, final BigDecimal totalDisbursementCharges) { this.id = id; + this.accountNo = accountNo; this.externalId = externalId; this.clientId = clientId; this.clientName = clientName; @@ -349,6 +353,10 @@ public class LoanBasicDetailsData { return id; } + public String getAccountNo() { + return this.accountNo; + } + public String getExternalId() { return externalId; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 7c9c6fbb1..45e0312b1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -35,6 +35,7 @@ import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom; +import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; @@ -64,9 +65,17 @@ import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.useradministration.domain.AppUser; @Entity -@Table(name = "m_loan", uniqueConstraints = @UniqueConstraint(columnNames = { "external_id" })) +@Table(name = "m_loan", uniqueConstraints = { + @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"), + @UniqueConstraint(columnNames = { "external_id" }, name = "loan_externalid_UNIQUE") }) public class Loan extends AbstractAuditableCustom { + @Column(name = "account_no", length = 20, unique = true, nullable = false) + private String accountNumber; + + @Column(name = "external_id") + private String externalId; + @ManyToOne(optional = true) @JoinColumn(name = "client_id") private Client client; @@ -95,9 +104,6 @@ public class Loan extends AbstractAuditableCustom { @JoinColumn(name = "loan_transaction_strategy_id", nullable = true) private LoanTransactionProcessingStrategy transactionProcessingStrategy; - @Column(name = "external_id") - private String externalId; - @Embedded private LoanProductRelatedDetail loanRepaymentScheduleDetail; @@ -187,59 +193,54 @@ public class Loan extends AbstractAuditableCustom { @Column(name = "total_charges_due_at_disbursement_derived", scale = 6, precision = 19) private BigDecimal totalChargesDueAtDisbursement; + @Transient + private boolean accountNumberRequiresAutoGeneration = false; + @Transient private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessor = new LoanRepaymentScheduleTransactionProcessorFactory(); - @Deprecated - public static Loan createNew(final Fund fund, final Staff loanOfficer, - final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Client client, - final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges) { - return new Loan(client, null, fund, loanOfficer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, null, - loanCharges); - } - - @Deprecated - public static Loan createNew(final Fund fund, final Staff loanOfficer, - final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final Group group, - final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges) { - return new Loan(null, group, fund, loanOfficer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, null, - loanCharges); - } - - public static Loan newIndividualLoanApplication(final Client client, final LoanProduct loanProduct, final Fund fund, - final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, - final Set loanCharges) { - final LoanStatus status = null; - LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); - return new Loan(client, null, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, - loanCharges); - } - - public static Loan newGroupLoanApplication(final Group group, final LoanProduct loanProduct, final Fund fund, final Staff officer, - final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, - final Set loanCharges) { - final LoanStatus status = null; - LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); - return new Loan(null, group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, - loanCharges); - } - - public static Loan newIndividualLoanApplicationFromGroup(final Client client, final Group group, final LoanProduct loanProduct, + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final LoanProduct loanProduct, final Fund fund, final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, final Set loanCharges) { final LoanStatus status = null; LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); - return new Loan(client, group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, - loanCharges); + return new Loan(accountNo, client, null, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, + status, loanCharges); + } + + public static Loan newGroupLoanApplication(final String accountNo, final Group group, final LoanProduct loanProduct, final Fund fund, + final Staff officer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, + final Set loanCharges) { + final LoanStatus status = null; + LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); + return new Loan(accountNo, null, group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, + status, loanCharges); + } + + public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, + final LoanProduct loanProduct, final Fund fund, final Staff officer, + final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule, + final Set loanCharges) { + final LoanStatus status = null; + LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail(); + return new Loan(accountNo, client, group, fund, officer, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, + status, loanCharges); } protected Loan() { // } - private Loan(final Client client, final Group group, Fund fund, Staff loanOfficer, + private Loan(final String accountNo, final Client client, final Group group, Fund fund, Staff loanOfficer, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final Set loanCharges) { + + if (StringUtils.isBlank(accountNo)) { + this.accountNumber = new RandomPasswordGenerator(19).generate(); + this.accountNumberRequiresAutoGeneration = true; + } else { + this.accountNumber = accountNo; + } this.client = client; this.group = group; this.fund = fund; @@ -284,6 +285,14 @@ public class Loan extends AbstractAuditableCustom { private void updateTotalChargesDueAtDisbursement() { this.totalChargesDueAtDisbursement = deriveSumTotalOfChargesDueAtDisbursement(); } + + public boolean isAccountNumberRequiresAutoGeneration() { + return this.accountNumberRequiresAutoGeneration; + } + + public void setAccountNumberRequiresAutoGeneration(boolean accountNumberRequiresAutoGeneration) { + this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; + } public void addLoanCharge(final LoanCharge loanCharge) { @@ -545,7 +554,7 @@ public class Loan extends AbstractAuditableCustom { this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); } - public Map modifyLoanApplication(final JsonCommand command, final LoanChargeCommand[] loanChargeCommands, + public Map loanApplicationModification(final JsonCommand command, final LoanChargeCommand[] loanChargeCommands, final AprCalculator aprCalculator) { final Map actualChanges = this.loanRepaymentScheduleDetail.updateLoanApplicationAttributes(command, aprCalculator); @@ -557,6 +566,13 @@ public class Loan extends AbstractAuditableCustom { final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); + final String accountNoParamName = "accountNo"; + if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) { + final String newValue = command.stringValueOfParameterNamed(accountNoParamName); + actualChanges.put(accountNoParamName, newValue); + this.accountNumber = StringUtils.defaultIfEmpty(newValue, null); + } + final String externalIdParamName = "externalId"; if (command.isChangeInStringParameterNamed(externalIdParamName, this.externalId)) { final String newValue = command.stringValueOfParameterNamed(externalIdParamName); @@ -658,6 +674,35 @@ public class Loan extends AbstractAuditableCustom { this.interestChargedFromDate = null; } } + + if (getSubmittedOnDate().isAfter(new LocalDate())) { + final String errorMessage = "The date on which a loan is submitted cannot be in the future."; + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate()); + } + + if (getSubmittedOnDate().isAfter(getExpectedDisbursedOnLocalDate())) { + final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: " + + getExpectedDisbursedOnLocalDate().toString(); + throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage, + getSubmittedOnDate(), getExpectedDisbursedOnLocalDate()); + } + + // FIXME - this constraint doesnt really need to be here. should be + // possible to express loan term as say 12 months whilst also saying + // - that the repayment structure is 6 repayments every bi-monthly. +// validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType, numberOfRepayments, +// repaymentEvery, repaymentEveryType); +// +// validateRepaymentsStartingFromDateIsAfterDisbursementDate(dataValidationErrors, expectedDisbursementDate, +// repaymentsStartingFromDate); +// +// validateRepaymentsStartingFromDateAndInterestChargedFromDate(dataValidationErrors, expectedDisbursementDate, +// repaymentsStartingFromDate, interestChargedFromDate); + + // charges are optional + for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) { + validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); + } return actualChanges; } @@ -715,6 +760,8 @@ public class Loan extends AbstractAuditableCustom { LoanStatus statusEnum = lifecycleStateMachine.transition(LoanEvent.LOAN_CREATED, from); this.loanStatus = statusEnum.getValue(); + this.externalId = externalId; + this.termFrequency = loanSchedule.getLoanTermFrequency(); this.termPeriodFrequencyType = loanSchedule.getLoanTermPeriodFrequencyType().getValue(); @@ -749,8 +796,6 @@ public class Loan extends AbstractAuditableCustom { for (LoanCharge loanCharge : getNullPointerSafeLoanCharges()) { validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); } - - this.externalId = externalId; } private LocalDate determineExpectedMaturityDate() { @@ -1946,8 +1991,13 @@ public class Loan extends AbstractAuditableCustom { if (this.client != null) { officeId = this.client.getOffice().getId(); } else { -// officeId = this.group.getOffice().getId(); + // officeId = this.group.getOffice().getId(); } return officeId; } + + public void updateAccountNo(final String newAccountNo) { + this.accountNumber = newAccountNo; + this.accountNumberRequiresAutoGeneration=false; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java index 7ca46f42f..a520c0785 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/handler/LoanApplicationModificationCommandHandler.java @@ -22,6 +22,6 @@ public class LoanApplicationModificationCommandHandler implements NewCommandSour @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.modifyLoanApplication(command.entityId(), command); + return this.writePlatformService.modifyLoanApplication(command.getLoanId(), command); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java deleted file mode 100644 index b69603532..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/query/CalculateLoanScheduleQuery.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.loanschedule.query; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; -import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; - -/** - * Immutable query used when auto-calculating loan schedules based on loan - * terms. - */ -public class CalculateLoanScheduleQuery { - - private final Long productId; - private final BigDecimal principal; - private final Integer loanTermFrequency; - private final Integer loanTermFrequencyType; - private final Integer repaymentEvery; - private final Integer repaymentFrequencyType; - private final Integer numberOfRepayments; - private final BigDecimal interestRatePerPeriod; - private final Integer interestRateFrequencyType; - private final Integer amortizationType; - private final Integer interestType; - private final Integer interestCalculationPeriodType; - private final LocalDate expectedDisbursementDate; - private final LocalDate repaymentsStartingFromDate; - private final LocalDate interestChargedFromDate; - private final LoanChargeCommand[] charges; - - public CalculateLoanScheduleQuery(final Long productId, final BigDecimal principal, final BigDecimal interestRatePerPeriod, - Integer interestRateFrequencyMethod, final Integer interestMethod, final Integer interestCalculationPeriodMethod, - final Integer repaymentEvery, final Integer repaymentFrequency, final Integer numberOfRepayments, Integer amortizationMethod, - final Integer loanTermFrequency, final Integer loanTermFrequencyType, final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, final LocalDate interestCalculatedFromDate, final LoanChargeCommand[] charges) { - - this.productId = productId; - this.principal = principal; - this.interestRatePerPeriod = interestRatePerPeriod; - this.interestRateFrequencyType = interestRateFrequencyMethod; - this.interestType = interestMethod; - this.interestCalculationPeriodType = interestCalculationPeriodMethod; - this.repaymentEvery = repaymentEvery; - this.repaymentFrequencyType = repaymentFrequency; - this.numberOfRepayments = numberOfRepayments; - this.amortizationType = amortizationMethod; - this.loanTermFrequency = loanTermFrequency; - this.loanTermFrequencyType = loanTermFrequencyType; - - this.expectedDisbursementDate = expectedDisbursementDate; - this.repaymentsStartingFromDate = repaymentsStartingFromDate; - this.interestChargedFromDate = interestCalculatedFromDate; - this.charges = charges; - } - - public Long getProductId() { - return productId; - } - - public BigDecimal getPrincipal() { - return principal; - } - - public Integer getLoanTermFrequency() { - return loanTermFrequency; - } - - public Integer getLoanTermFrequencyType() { - return loanTermFrequencyType; - } - - public Integer getRepaymentEvery() { - return repaymentEvery; - } - - public Integer getRepaymentFrequencyType() { - return repaymentFrequencyType; - } - - public Integer getNumberOfRepayments() { - return numberOfRepayments; - } - - public BigDecimal getInterestRatePerPeriod() { - return interestRatePerPeriod; - } - - public Integer getInterestRateFrequencyType() { - return interestRateFrequencyType; - } - - public Integer getAmortizationType() { - return amortizationType; - } - - public Integer getInterestType() { - return interestType; - } - - public Integer getInterestCalculationPeriodType() { - return interestCalculationPeriodType; - } - - public LocalDate getExpectedDisbursementDate() { - return expectedDisbursementDate; - } - - public LocalDate getRepaymentsStartingFromDate() { - return repaymentsStartingFromDate; - } - - public LocalDate getInterestChargedFromDate() { - return interestChargedFromDate; - } - - public LoanChargeCommand[] getCharges() { - return charges; - } - - public List validate() { - - final List dataValidationErrors = new ArrayList(); - - DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - - baseDataValidator.reset().parameter("principal").value(this.principal).notNull().positiveAmount(); - - baseDataValidator.reset().parameter("loanTermFrequency").value(this.loanTermFrequency).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("loanTermFrequencyType").value(this.loanTermFrequencyType).notNull().inMinMaxRange(0, 4); - - baseDataValidator.reset().parameter("numberOfRepayments").value(this.numberOfRepayments).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("repaymentEvery").value(this.repaymentEvery).notNull().integerGreaterThanZero(); - baseDataValidator.reset().parameter("repaymentFrequencyType").value(this.repaymentFrequencyType).notNull().inMinMaxRange(0, 3); - - // FIXME - this constraint doesnt really need to be here. should be - // possible to express loan term as say 12 months whilst also saying - // - that the repayment structure is 6 repayments every bi-monthly. - if (this.loanTermFrequencyType != null && !this.loanTermFrequencyType.equals(this.repaymentFrequencyType)) { - ApiParameterError error = ApiParameterError.parameterError( - "validation.msg.loan.loanTermFrequencyType.not.the.same.as.repaymentFrequencyType", - "The parameters loanTermFrequencyType and repaymentFrequencyType must be the same.", "loanTermFrequencyType", - this.loanTermFrequencyType, this.repaymentFrequencyType); - dataValidationErrors.add(error); - } else { - if (this.loanTermFrequency != null && this.repaymentEvery != null && this.numberOfRepayments != null) { - int suggestsedLoanTerm = this.repaymentEvery * this.numberOfRepayments; - if (this.loanTermFrequency.intValue() < suggestsedLoanTerm) { - ApiParameterError error = ApiParameterError - .parameterError( - "validation.msg.loan.loanTermFrequency.less.than.repayment.structure.suggests", - "The parameter loanTermFrequency is less than the suggest loan term as indicated by numberOfRepayments and repaymentEvery.", - "loanTermFrequency", this.loanTermFrequency, this.numberOfRepayments, this.repaymentEvery); - dataValidationErrors.add(error); - } - } - } - - baseDataValidator.reset().parameter("interestRatePerPeriod").value(this.interestRatePerPeriod).notNull(); - baseDataValidator.reset().parameter("interestRateFrequencyType").value(this.interestRateFrequencyType).notNull() - .inMinMaxRange(0, 3); - baseDataValidator.reset().parameter("amortizationType").value(this.amortizationType).notNull().inMinMaxRange(0, 1); - baseDataValidator.reset().parameter("interestType").value(this.interestType).notNull().inMinMaxRange(0, 1); - baseDataValidator.reset().parameter("interestCalculationPeriodType").value(this.interestCalculationPeriodType).notNull() - .inMinMaxRange(0, 1); - - baseDataValidator.reset().parameter("expectedDisbursementDate").value(this.expectedDisbursementDate).notNull(); - - if (this.expectedDisbursementDate != null) { - if (this.repaymentsStartingFromDate != null && this.expectedDisbursementDate.isAfter(this.repaymentsStartingFromDate)) { - ApiParameterError error = ApiParameterError.parameterError( - "validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", - "The parameter expectedDisbursementDate has a date which falls after the given first repayment date.", - "expectedDisbursementDate", this.expectedDisbursementDate, this.repaymentsStartingFromDate); - dataValidationErrors.add(error); - } - } - - if (this.repaymentsStartingFromDate != null && this.interestChargedFromDate == null) { - - ApiParameterError error = ApiParameterError.parameterError( - "validation.msg.loan.interestCalculatedFromDate.must.be.entered.when.using.repayments.startfrom.field", - "The parameter interestCalculatedFromDate cannot be empty when first repayment date is provided.", - "interestChargedFromDate", this.repaymentsStartingFromDate); - dataValidationErrors.add(error); - } else if (this.repaymentsStartingFromDate == null && this.interestChargedFromDate != null) { - - // validate interestCalculatedFromDate is after or on - // repaymentsStartingFromDate - if (this.expectedDisbursementDate.isAfter(this.interestChargedFromDate)) { - ApiParameterError error = ApiParameterError.parameterError( - "validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date", - "The parameter interestCalculatedFromDate cannot be before the date given for expected disbursement.", - "interestChargedFromDate", this.interestChargedFromDate, this.expectedDisbursementDate); - dataValidationErrors.add(error); - } - } - - return dataValidationErrors; - - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 99a36b57d..2e277effc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -1,15 +1,10 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.service; -import java.util.List; - import org.mifosplatform.infrastructure.core.api.JsonQuery; -import org.mifosplatform.infrastructure.core.data.ApiParameterError; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule; -import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; -import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -17,12 +12,12 @@ import org.springframework.stereotype.Service; public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService { private final PlatformSecurityContext context; - private final CalculateLoanScheduleQueryFromApiJsonDeserializer fromApiJsonDeserializer; + private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer; private final LoanScheduleAssembler loanScheduleAssembler; @Autowired public LoanScheduleCalculationPlatformServiceImpl(final PlatformSecurityContext context, - final CalculateLoanScheduleQueryFromApiJsonDeserializer fromApiJsonDeserializer, + final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer, final LoanScheduleAssembler loanScheduleAssembler) { this.context = context; this.fromApiJsonDeserializer = fromApiJsonDeserializer; @@ -33,12 +28,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC public LoanScheduleData calculateLoanSchedule(final JsonQuery query) { context.authenticatedUser(); - final CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.fromApiJsonDeserializer.commandFromApiJson(query.json()); - final List dataValidationErrors = calculateLoanScheduleQuery.validate(); - - if (!dataValidationErrors.isEmpty()) { - throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); - } + this.fromApiJsonDeserializer.validate(query.json()); final LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(query.parsedJson()); return loanSchedule.generate(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java deleted file mode 100644 index 7d427ce37..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonDeserializer.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.serialization; - -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang.StringUtils; -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; -import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer; -import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer; -import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; - -/** - * Implementation of {@link FromApiJsonDeserializer} for - * {@link CalculateLoanScheduleQuery}'s. - */ -@Component -public final class CalculateLoanScheduleQueryFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer { - - /** - * The parameters supported for this command. - */ - final Set supportedParameters = new HashSet(Arrays.asList("clientId", "groupId", "productId", "externalId", "fundId", - "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", - "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", - "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", - "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", - "loanOfficerId", "id")); - - private final FromJsonHelper fromApiJsonHelper; - - @Autowired - public CalculateLoanScheduleQueryFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } - - @Override - public CalculateLoanScheduleQuery commandFromApiJson(final String json) { - - if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - - final Type typeOfMap = new TypeToken>() {}.getType(); - fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); - - final JsonElement element = fromApiJsonHelper.parse(json); - final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); - final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); - final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); - final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); - final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); - final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", - element); - - final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); - final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); - final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); - final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); - final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element); - final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element); - final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element); - final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); - final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); - - // /// - LoanChargeCommand[] charges = null; - if (element.isJsonObject()) { - final JsonObject topLevelJsonElement = element.getAsJsonObject(); - final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); - final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); - if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { - - final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); - charges = new LoanChargeCommand[array.size()]; - for (int i = 0; i < array.size(); i++) { - - final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); - - // final Long id = fromApiJsonHelper.extractLongNamed("id", - // loanChargeElement); - final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); - final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); - final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); - final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, - locale); - final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, - dateFormat, locale); - - charges[i] = new LoanChargeCommand(chargeId, amount, chargeTimeType, chargeCalculationType, specifiedDueDate); - } - } - } - // / - - return new CalculateLoanScheduleQuery(productId, principal, interestRatePerPeriod, interestRateFrequencyType, interestType, - interestCalculationPeriodType, repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationType, - loanTermFrequency, loanTermFrequencyType, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, - charges); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java new file mode 100644 index 000000000..215572dde --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -0,0 +1,244 @@ +package org.mifosplatform.portfolio.loanaccount.serialization; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +@Component +public final class CalculateLoanScheduleQueryFromApiJsonHelper { + + /** + * The parameters supported for this command. + */ + final Set supportedParameters = new HashSet(Arrays.asList("clientId", "groupId", "productId", "accountNo", + "externalId", "fundId", "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", + "repaymentEvery", "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", + "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", + "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", + "loanOfficerId", "id")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public CalculateLoanScheduleQueryFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + + final JsonElement element = fromApiJsonHelper.parse(json); + + final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero(); + + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount(); + + final String loanTermFrequencyParameterName = "loanTermFrequency"; + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element); + baseDataValidator.reset().parameter(loanTermFrequencyParameterName).value(loanTermFrequency).notNull().integerGreaterThanZero(); + + final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType"; + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyTypeParameterName, element); + baseDataValidator.reset().parameter(loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull().inMinMaxRange(0, 3); + + final String numberOfRepaymentsParameterName = "numberOfRepayments"; + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notNull().integerGreaterThanZero(); + + final String repaymentEveryParameterName = "repaymentEvery"; + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element); + baseDataValidator.reset().parameter(repaymentEveryParameterName).value(repaymentEvery).notNull().integerGreaterThanZero(); + + final String repaymentEveryFrequencyTypeParameterName = "repaymentFrequencyType"; + final Integer repaymentEveryType = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryFrequencyTypeParameterName, + element); + baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() + .inMinMaxRange(0, 3); + + // FIXME - this constraint doesnt really need to be here. should be + // possible to express loan term as say 12 months whilst also saying + // - that the repayment structure is 6 repayments every bi-monthly. + validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType, numberOfRepayments, + repaymentEvery, repaymentEveryType); + + final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, + element); + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull().positiveAmount(); + + final String interestRateFrequencyTypeParameterName = "interestRateFrequencyType"; + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed(interestRateFrequencyTypeParameterName, + element); + baseDataValidator.reset().parameter(interestRateFrequencyTypeParameterName).value(interestRateFrequencyType).notNull() + .inMinMaxRange(0, 3); + + final String interestTypeParameterName = "interestType"; + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element); + baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); + + final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed( + interestCalculationPeriodTypeParameterName, element); + baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull() + .inMinMaxRange(0, 1); + + final String amortizationTypeParameterName = "amortizationType"; + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element); + baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); + + final String expectedDisbursementDateParameterName = "expectedDisbursementDate"; + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, element); + baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull(); + + LocalDate repaymentsStartingFromDate = null; + final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate"; + if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) { + repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName, element); + baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull().notNull(); + } + + LocalDate interestChargedFromDate = null; + final String interestChargedFromDateParameterName = "interestChargedFromDate"; + if (fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) { + interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed(interestChargedFromDateParameterName, element); + baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull().notNull(); + } + + validateRepaymentsStartingFromDateIsAfterDisbursementDate(dataValidationErrors, expectedDisbursementDate, + repaymentsStartingFromDate); + + validateRepaymentsStartingFromDateAndInterestChargedFromDate(dataValidationErrors, expectedDisbursementDate, + repaymentsStartingFromDate, interestChargedFromDate); + + final LoanChargeCommand[] charges = extractInToLoanChargeCommands(element); + if (charges != null) { + for (LoanChargeCommand loanChargeCommand : charges) { + try { + loanChargeCommand.validateForCreate(); + } catch (PlatformApiDataValidationException e) { + dataValidationErrors.addAll(e.getErrors()); + } + } + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } + + private void validateSelectedPeriodFrequencyTypeIsTheSame(final List dataValidationErrors, + final Integer loanTermFrequency, final Integer loanTermFrequencyType, final Integer numberOfRepayments, + final Integer repaymentEvery, final Integer repaymentEveryType) { + if (loanTermFrequencyType != null && !loanTermFrequencyType.equals(repaymentEveryType)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.loanTermFrequencyType.not.the.same.as.repaymentFrequencyType", + "The parameters loanTermFrequencyType and repaymentFrequencyType must be the same.", "loanTermFrequencyType", + loanTermFrequencyType, repaymentEveryType); + dataValidationErrors.add(error); + } else { + if (loanTermFrequency != null && repaymentEvery != null && numberOfRepayments != null) { + int suggestsedLoanTerm = repaymentEvery * numberOfRepayments; + if (loanTermFrequency.intValue() < suggestsedLoanTerm) { + ApiParameterError error = ApiParameterError + .parameterError( + "validation.msg.loan.loanTermFrequency.less.than.repayment.structure.suggests", + "The parameter loanTermFrequency is less than the suggest loan term as indicated by numberOfRepayments and repaymentEvery.", + "loanTermFrequency", loanTermFrequency, numberOfRepayments, repaymentEvery); + dataValidationErrors.add(error); + } + } + } + } + + private void validateRepaymentsStartingFromDateAndInterestChargedFromDate(final List dataValidationErrors, + final LocalDate expectedDisbursementDate, LocalDate repaymentsStartingFromDate, LocalDate interestChargedFromDate) { + if (repaymentsStartingFromDate != null && interestChargedFromDate == null) { + + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.interestChargedFromDate.must.be.entered.when.using.repayments.startfrom.field", + "The parameter interestChargedFromDate cannot be empty when repaymentsStartingFromDate is provided.", + "interestChargedFromDate", repaymentsStartingFromDate); + dataValidationErrors.add(error); + } else if (repaymentsStartingFromDate == null && interestChargedFromDate != null) { + + if (expectedDisbursementDate != null && expectedDisbursementDate.isAfter(interestChargedFromDate)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date", + "The parameter interestChargedFromDate cannot be before the date given for expectedDisbursementDate.", + "interestChargedFromDate", interestChargedFromDate, expectedDisbursementDate); + dataValidationErrors.add(error); + } + } + } + + private void validateRepaymentsStartingFromDateIsAfterDisbursementDate(final List dataValidationErrors, + final LocalDate expectedDisbursementDate, LocalDate repaymentsStartingFromDate) { + if (expectedDisbursementDate != null) { + if (repaymentsStartingFromDate != null && expectedDisbursementDate.isAfter(repaymentsStartingFromDate)) { + ApiParameterError error = ApiParameterError.parameterError( + "validation.msg.loan.expectedDisbursementDate.cannot.be.after.first.repayment.date", + "The parameter expectedDisbursementDate has a date which falls after the date for repaymentsStartingFromDate.", + "expectedDisbursementDate", expectedDisbursementDate, repaymentsStartingFromDate); + dataValidationErrors.add(error); + } + } + } + + private LoanChargeCommand[] extractInToLoanChargeCommands(final JsonElement element) { + LoanChargeCommand[] charges = null; + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + charges = new LoanChargeCommand[array.size()]; + for (int i = 0; i < array.size(); i++) { + + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + + final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, + locale); + final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, + dateFormat, locale); + + charges[i] = new LoanChargeCommand(chargeId, amount, chargeTimeType, chargeCalculationType, specifiedDueDate); + } + } + } + return charges; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java deleted file mode 100644 index f5b88c4ea..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonDeserializer.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.serialization; - -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.lang.StringUtils; -import org.joda.time.LocalDate; -import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; -import org.mifosplatform.infrastructure.core.serialization.AbstractFromApiJsonDeserializer; -import org.mifosplatform.infrastructure.core.serialization.FromApiJsonDeserializer; -import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; -import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; - -/** - * Implementation of {@link FromApiJsonDeserializer} for - * {@link LoanApplicationCommand}'s. - */ -@Component -public final class LoanApplicationCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer { - - /** - * The parameters supported for this command. - */ - final Set supportedParameters = new HashSet(Arrays.asList("clientId", "groupId", "productId", "externalId", "fundId", - "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", - "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", - "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", - "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", - "loanOfficerId", "id")); - - private final FromJsonHelper fromApiJsonHelper; - - @Autowired - public LoanApplicationCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { - this.fromApiJsonHelper = fromApiJsonHelper; - } - - @Override - public LoanApplicationCommand commandFromApiJson(final String json) { - - if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } - - final Type typeOfMap = new TypeToken>() {}.getType(); - fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); - - final JsonElement element = fromApiJsonHelper.parse(json); - final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); - final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); - final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); - final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element); - final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); - final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element); - final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed("transactionProcessingStrategyId", element); - - final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); - final String submittedOnNote = fromApiJsonHelper.extractStringNamed("submittedOnNote", element); - - final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); - final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); - final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element); - final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); - final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType", - element); - - // final InterestMethod interestMethod = - // InterestMethod.fromInt(interestType); - // final PeriodFrequencyType interestRatePeriodFrequencyType = - // PeriodFrequencyType.fromInt(interestRateFrequencyType); - // final InterestCalculationPeriodMethod interestCalculationPeriodMethod - // = InterestCalculationPeriodMethod - // .fromInt(interestCalculationPeriodType); - - final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); - final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); - final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element); - final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element); - // final PeriodFrequencyType repaymentPeriodFrequencyType = - // PeriodFrequencyType.fromInt(repaymentFrequencyType); - // final AmortizationMethod amortizationMethod = - // AmortizationMethod.fromInt(amortizationType); - - final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element); - final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element); - // final PeriodFrequencyType loanTermPeriodFrequencyType = - // PeriodFrequencyType.fromInt(loanTermFrequencyType); - - final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element); - final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); - final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); - - final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); - - // /// - LoanChargeCommand[] charges = null; - if (element.isJsonObject()) { - final JsonObject topLevelJsonElement = element.getAsJsonObject(); - final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); - final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); - if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { - - final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); - charges = new LoanChargeCommand[array.size()]; - for (int i = 0; i < array.size(); i++) { - - final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); - - // final Long id = fromApiJsonHelper.extractLongNamed("id", - // loanChargeElement); - final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); - final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); - final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); - final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, - locale); - final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, - dateFormat, locale); - - charges[i] = new LoanChargeCommand(chargeId, amount, chargeTimeType, chargeCalculationType, specifiedDueDate); - } - } - } - - return new LoanApplicationCommand(clientId, groupId, productId, externalId, fundId, transactionProcessingStrategyId, - submittedOnDate, submittedOnNote, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, principal, - interestRatePerPeriod, interestRateFrequencyType, interestType, interestCalculationPeriodType, repaymentEvery, - repaymentFrequencyType, numberOfRepayments, amortizationType, loanTermFrequency, loanTermFrequencyType, inArrearsTolerance, - charges, loanOfficerId); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java new file mode 100644 index 000000000..073f35249 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -0,0 +1,457 @@ +package org.mifosplatform.portfolio.loanaccount.serialization; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; + +@Component +public final class LoanApplicationCommandFromApiJsonHelper { + + /** + * The parameters supported for this command. + */ + final Set supportedParameters = new HashSet(Arrays.asList("id", "clientId", "groupId", "productId", "accountNo", + "externalId", "fundId", "loanOfficerId", "transactionProcessingStrategyId", "principal", "inArrearsTolerance", + "repaymentEvery", "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "charges", "repaymentFrequencyType", + "interestRatePerPeriod", "interestRateFrequencyType", "interestType", "interestCalculationPeriodType", "amortizationType", + "expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", + "locale", "dateFormat")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public LoanApplicationCommandFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + + final JsonElement element = fromApiJsonHelper.parse(json); + final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element); + if (clientId != null) { + baseDataValidator.reset().parameter("clientId").value(clientId).longGreaterThanZero(); + } + + final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element); + if (groupId != null) { + baseDataValidator.reset().parameter("groupId").value(groupId).longGreaterThanZero(); + } + + if (clientId == null && groupId == null) { + baseDataValidator.reset().parameter("clientId").value(clientId).notNull().integerGreaterThanZero(); + } + + final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); + baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero(); + + final String accountNoParameterName = "accountNo"; + if (fromApiJsonHelper.parameterExists(accountNoParameterName, element)) { + final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParameterName, element); + baseDataValidator.reset().parameter(accountNoParameterName).value(accountNo).ignoreIfNull().notExceedingLengthOf(20); + } + + final String externalIdParameterName = "externalId"; + if (fromApiJsonHelper.parameterExists(externalIdParameterName, element)) { + final String externalId = fromApiJsonHelper.extractStringNamed(externalIdParameterName, element); + baseDataValidator.reset().parameter(externalIdParameterName).value(externalId).ignoreIfNull().notExceedingLengthOf(100); + } + + final String fundIdParameterName = "fundId"; + if (fromApiJsonHelper.parameterExists(fundIdParameterName, element)) { + final Long fundId = fromApiJsonHelper.extractLongNamed(fundIdParameterName, element); + baseDataValidator.reset().parameter(fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero(); + } + + final String loanOfficerIdParameterName = "loanOfficerId"; + if (fromApiJsonHelper.parameterExists(loanOfficerIdParameterName, element)) { + final Long loanOfficerId = fromApiJsonHelper.extractLongNamed(loanOfficerIdParameterName, element); + baseDataValidator.reset().parameter(loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull().integerGreaterThanZero(); + } + + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); + baseDataValidator.reset().parameter("principal").value(principal).notNull().positiveAmount(); + + final String loanTermFrequencyParameterName = "loanTermFrequency"; + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element); + baseDataValidator.reset().parameter(loanTermFrequencyParameterName).value(loanTermFrequency).notNull().integerGreaterThanZero(); + + final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType"; + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyTypeParameterName, element); + baseDataValidator.reset().parameter(loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull().inMinMaxRange(0, 3); + + final String numberOfRepaymentsParameterName = "numberOfRepayments"; + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notNull().integerGreaterThanZero(); + + final String repaymentEveryParameterName = "repaymentEvery"; + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element); + baseDataValidator.reset().parameter(repaymentEveryParameterName).value(repaymentEvery).notNull().integerGreaterThanZero(); + + final String repaymentEveryFrequencyTypeParameterName = "repaymentFrequencyType"; + final Integer repaymentEveryType = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryFrequencyTypeParameterName, + element); + baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() + .inMinMaxRange(0, 3); + + final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, + element); + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull().positiveAmount(); + + final String interestRateFrequencyTypeParameterName = "interestRateFrequencyType"; + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed(interestRateFrequencyTypeParameterName, + element); + baseDataValidator.reset().parameter(interestRateFrequencyTypeParameterName).value(interestRateFrequencyType).notNull() + .inMinMaxRange(0, 3); + + final String interestTypeParameterName = "interestType"; + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element); + baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); + + final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed( + interestCalculationPeriodTypeParameterName, element); + baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull() + .inMinMaxRange(0, 1); + + final String amortizationTypeParameterName = "amortizationType"; + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element); + baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); + + final String expectedDisbursementDateParameterName = "expectedDisbursementDate"; + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, element); + baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull(); + + final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate"; + if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) { + final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName, + element); + baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull().notNull(); + } + + final String interestChargedFromDateParameterName = "interestChargedFromDate"; + if (fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) { + final LocalDate interestChargedFromDate = fromApiJsonHelper + .extractLocalDateNamed(interestChargedFromDateParameterName, element); + baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull().notNull(); + } + + final String inArrearsToleranceParameterName = "inArrearsTolerance"; + if (fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) { + final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName, + element); + baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull().positiveAmount(); + } + + final String submittedOnDateParameterName = "submittedOnDate"; + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParameterName, element); + if (submittedOnDate == null) { + baseDataValidator.reset().parameter(submittedOnDateParameterName).value(submittedOnDate).notNull(); + } + + final String submittedOnNoteParameterName = "submittedOnNote"; + if (fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) { + final String submittedOnNote = fromApiJsonHelper.extractStringNamed(submittedOnNoteParameterName, element); + baseDataValidator.reset().parameter(submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull().notExceedingLengthOf(500); + } + + // not optional at present but might make it so to allow it to pick up + // on only available strategy + // by default which would probably be the norm. + final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId"; + final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed(transactionProcessingStrategyIdParameterName, + element); + baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId).notNull() + .integerGreaterThanZero(); + + final LoanChargeCommand[] charges = extractInToLoanChargeCommands(element); + if (charges != null) { + for (LoanChargeCommand loanChargeCommand : charges) { + try { + loanChargeCommand.validateForCreate(); + } catch (PlatformApiDataValidationException e) { + dataValidationErrors.addAll(e.getErrors()); + } + } + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } + + public void validateForModify(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters); + + final List dataValidationErrors = new ArrayList(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + final JsonElement element = fromApiJsonHelper.parse(json); + boolean atLeastOneParameterPassedForUpdate = false; + + final String clientIdParameterName = "clientId"; + if (fromApiJsonHelper.parameterExists(clientIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long clientId = fromApiJsonHelper.extractLongNamed(clientIdParameterName, element); + baseDataValidator.reset().parameter(clientIdParameterName).value(clientId).notNull().integerGreaterThanZero(); + } + + final String groupIdParameterName = "groupId"; + if (fromApiJsonHelper.parameterExists(groupIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long groupId = fromApiJsonHelper.extractLongNamed(groupIdParameterName, element); + baseDataValidator.reset().parameter(groupIdParameterName).value(groupId).notNull().integerGreaterThanZero(); + } + + final String productIdParameterName = "productId"; + if (fromApiJsonHelper.parameterExists(productIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long productId = fromApiJsonHelper.extractLongNamed(productIdParameterName, element); + baseDataValidator.reset().parameter(productIdParameterName).value(productId).notNull().integerGreaterThanZero(); + } + + final String accountNoParameterName = "accountNo"; + if (fromApiJsonHelper.parameterExists(accountNoParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final String accountNo = fromApiJsonHelper.extractStringNamed(accountNoParameterName, element); + baseDataValidator.reset().parameter(accountNoParameterName).value(accountNo).notBlank().notExceedingLengthOf(20); + } + + final String externalIdParameterName = "externalId"; + if (fromApiJsonHelper.parameterExists(externalIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final String externalId = fromApiJsonHelper.extractStringNamed(externalIdParameterName, element); + baseDataValidator.reset().parameter(externalIdParameterName).value(externalId).ignoreIfNull().notExceedingLengthOf(100); + } + + final String fundIdParameterName = "fundId"; + if (fromApiJsonHelper.parameterExists(fundIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long fundId = fromApiJsonHelper.extractLongNamed(fundIdParameterName, element); + baseDataValidator.reset().parameter(fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero(); + } + + final String loanOfficerIdParameterName = "loanOfficerId"; + if (fromApiJsonHelper.parameterExists(loanOfficerIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long loanOfficerId = fromApiJsonHelper.extractLongNamed(loanOfficerIdParameterName, element); + baseDataValidator.reset().parameter(loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull().integerGreaterThanZero(); + } + + final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId"; + if (fromApiJsonHelper.parameterExists(transactionProcessingStrategyIdParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Long transactionProcessingStrategyId = fromApiJsonHelper.extractLongNamed(transactionProcessingStrategyIdParameterName, + element); + baseDataValidator.reset().parameter(transactionProcessingStrategyIdParameterName).value(transactionProcessingStrategyId) + .notNull().integerGreaterThanZero(); + } + + final String principalParameterName = "principal"; + if (fromApiJsonHelper.parameterExists(principalParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalParameterName, element); + baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount(); + } + + final String inArrearsToleranceParameterName = "inArrearsTolerance"; + if (fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName, element); + baseDataValidator.reset().parameter(inArrearsToleranceParameterName).value(principal).ignoreIfNull().positiveAmount(); + } + + final String loanTermFrequencyParameterName = "loanTermFrequency"; + if (fromApiJsonHelper.parameterExists(loanTermFrequencyParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyParameterName, element); + baseDataValidator.reset().parameter(loanTermFrequencyParameterName).value(loanTermFrequency).notNull().integerGreaterThanZero(); + } + + final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType"; + if (fromApiJsonHelper.parameterExists(loanTermFrequencyTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed(loanTermFrequencyTypeParameterName, + element); + baseDataValidator.reset().parameter(loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull() + .inMinMaxRange(0, 3); + } + + final String numberOfRepaymentsParameterName = "numberOfRepayments"; + if (fromApiJsonHelper.parameterExists(numberOfRepaymentsParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed(numberOfRepaymentsParameterName, element); + baseDataValidator.reset().parameter(numberOfRepaymentsParameterName).value(numberOfRepayments).notNull() + .integerGreaterThanZero(); + } + + final String repaymentEveryParameterName = "repaymentEvery"; + if (fromApiJsonHelper.parameterExists(repaymentEveryParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryParameterName, element); + baseDataValidator.reset().parameter(repaymentEveryParameterName).value(repaymentEvery).notNull().integerGreaterThanZero(); + } + + final String repaymentEveryTypeParameterName = "repaymentFrequencyType"; + if (fromApiJsonHelper.parameterExists(repaymentEveryTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer repaymentEveryType = fromApiJsonHelper.extractIntegerWithLocaleNamed(repaymentEveryTypeParameterName, element); + baseDataValidator.reset().parameter(repaymentEveryTypeParameterName).value(repaymentEveryType).notNull().inMinMaxRange(0, 3); + } + + final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + if (fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, + element); + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull().positiveAmount(); + } + + final String interestRateFrequencyTypeParameterName = "interestRateFrequencyType"; + if (fromApiJsonHelper.parameterExists(interestRateFrequencyTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed( + interestRateFrequencyTypeParameterName, element); + baseDataValidator.reset().parameter(interestRateFrequencyTypeParameterName).value(interestRateFrequencyType).notNull() + .inMinMaxRange(0, 3); + } + + final String interestTypeParameterName = "interestType"; + if (fromApiJsonHelper.parameterExists(interestTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element); + baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); + } + + final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; + if (fromApiJsonHelper.parameterExists(interestCalculationPeriodTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed( + interestCalculationPeriodTypeParameterName, element); + baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull() + .inMinMaxRange(0, 1); + } + + final String amortizationTypeParameterName = "amortizationType"; + if (fromApiJsonHelper.parameterExists(amortizationTypeParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed(amortizationTypeParameterName, element); + baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); + } + + final String expectedDisbursementDateParameterName = "expectedDisbursementDate"; + if (fromApiJsonHelper.parameterExists(expectedDisbursementDateParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + + final String expectedDisbursementDateStr = fromApiJsonHelper.extractStringNamed(expectedDisbursementDateParameterName, element); + baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDateStr).notBlank(); + + final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, + element); + baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull(); + } + + final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate"; + if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName, + element); + baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull(); + } + + final String interestChargedFromDateParameterName = "interestChargedFromDate"; + if (fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final LocalDate interestChargedFromDate = fromApiJsonHelper + .extractLocalDateNamed(interestChargedFromDateParameterName, element); + baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull(); + } + + final String submittedOnDateParameterName = "submittedOnDate"; + if (fromApiJsonHelper.parameterExists(submittedOnDateParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final LocalDate submittedOnDate = fromApiJsonHelper.extractLocalDateNamed(submittedOnDateParameterName, element); + baseDataValidator.reset().parameter(submittedOnDateParameterName).value(submittedOnDate).notNull(); + } + + final String submittedOnNoteParameterName = "submittedOnNote"; + if (fromApiJsonHelper.parameterExists(submittedOnNoteParameterName, element)) { + atLeastOneParameterPassedForUpdate = true; + final String submittedOnNote = fromApiJsonHelper.extractStringNamed(submittedOnNoteParameterName, element); + baseDataValidator.reset().parameter(submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull() + .notExceedingLengthOf(500); + } + + if (!atLeastOneParameterPassedForUpdate) { + final Object forceError = null; + baseDataValidator.reset().anyOfNotNull(forceError); + } + + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); } + } + + public LoanChargeCommand[] extractLoanCharges(final String json) { + final JsonElement element = fromApiJsonHelper.parse(json); + return extractInToLoanChargeCommands(element); + } + + private LoanChargeCommand[] extractInToLoanChargeCommands(final JsonElement element) { + LoanChargeCommand[] charges = null; + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement); + final Locale locale = fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) { + + final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray(); + charges = new LoanChargeCommand[array.size()]; + for (int i = 0; i < array.size(); i++) { + + final JsonObject loanChargeElement = array.get(i).getAsJsonObject(); + + final Long chargeId = fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement); + final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale); + final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale); + final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", loanChargeElement, + locale); + final LocalDate specifiedDueDate = fromApiJsonHelper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, + dateFormat, locale); + + charges[i] = new LoanChargeCommand(chargeId, amount, chargeTimeType, chargeCalculationType, specifiedDueDate); + } + } + } + return charges; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 37173c650..73c6299c1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -1,6 +1,5 @@ package org.mifosplatform.portfolio.loanaccount.service; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -10,20 +9,21 @@ import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonQuery; -import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; -import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; +import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepository; import org.mifosplatform.portfolio.client.domain.Note; import org.mifosplatform.portfolio.client.domain.NoteRepository; import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; import org.mifosplatform.portfolio.fund.domain.Fund; -import org.mifosplatform.portfolio.loanaccount.command.LoanApplicationCommand; +import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanStateTransitionCommand; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; @@ -35,17 +35,18 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; -import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonDeserializer; -import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper; import org.mifosplatform.portfolio.loanaccount.serialization.LoanStateTransitionCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -54,10 +55,11 @@ import com.google.gson.JsonElement; @Service public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements LoanApplicationWritePlatformService { + private final static Logger logger = LoggerFactory.getLogger(LoanApplicationWritePlatformServiceJpaRepositoryImpl.class); + private final PlatformSecurityContext context; private final FromJsonHelper fromJsonHelper; - private final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer; - private final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer; + private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer; private final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer; private final LoanRepository loanRepository; private final NoteRepository noteRepository; @@ -67,21 +69,20 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final LoanProductRepository loanProductRepository; private final LoanChargeAssembler loanChargeAssembler; private final AprCalculator aprCalculator; + private final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory; @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, - final LoanApplicationCommandFromApiJsonDeserializer fromApiJsonDeserializer, + final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, final LoanStateTransitionCommandFromApiJsonDeserializer loanStateTransitionCommandFromApiJsonDeserializer, - final CalculateLoanScheduleQueryFromApiJsonDeserializer calculateLoanScheduleQueryFromApiJsonDeserializer, final AprCalculator aprCalculator, final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler, final LoanRepository loanRepository, final NoteRepository noteRepository, final LoanScheduleCalculationPlatformService calculationPlatformService, final ClientRepository clientRepository, - final LoanProductRepository loanProductRepository) { + final LoanProductRepository loanProductRepository, final AccountNumberGeneratorFactory accountIdentifierGeneratorFactory) { this.context = context; this.fromJsonHelper = fromJsonHelper; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.loanStateTransitionCommandFromApiJsonDeserializer = loanStateTransitionCommandFromApiJsonDeserializer; - this.calculateLoanScheduleQueryFromApiJsonDeserializer = calculateLoanScheduleQueryFromApiJsonDeserializer; this.aprCalculator = aprCalculator; this.loanAssembler = loanAssembler; this.loanChargeAssembler = loanChargeAssembler; @@ -90,6 +91,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.calculationPlatformService = calculationPlatformService; this.clientRepository = clientRepository; this.loanProductRepository = loanProductRepository; + this.accountIdentifierGeneratorFactory = accountIdentifierGeneratorFactory; } private boolean isBeforeToday(final LocalDate date) { @@ -107,23 +109,19 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa context.authenticatedUser(); - final CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.calculateLoanScheduleQueryFromApiJsonDeserializer - .commandFromApiJson(command.json()); - final List loanScheduleErrors = calculateLoanScheduleQuery.validate(); - - final LoanApplicationCommand loanApplicationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); - final List loanApplicationErrors = loanApplicationCommand.validate(); - - final List combinedErrors = new ArrayList(loanScheduleErrors); - combinedErrors.addAll(loanApplicationErrors); - - if (!combinedErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", - "Validation errors exist.", combinedErrors); } + this.fromApiJsonDeserializer.validateForCreate(command.json()); final Loan newLoanApplication = loanAssembler.assembleFrom(command); this.loanRepository.save(newLoanApplication); + if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) { + final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory + .determineLoanAccountNoGenerator(newLoanApplication.getId()); + newLoanApplication.updateAccountNo(accountNoGenerator.generate()); + this.loanRepository.save(newLoanApplication); + } + final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); if (StringUtils.isNotBlank(submittedOnNote)) { Note note = Note.loanNote(newLoanApplication, submittedOnNote); @@ -144,92 +142,115 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa @Override public CommandProcessingResult modifyLoanApplication(final Long loanId, final JsonCommand command) { - context.authenticatedUser(); + try { + context.authenticatedUser(); - final CalculateLoanScheduleQuery calculateLoanScheduleQuery = this.calculateLoanScheduleQueryFromApiJsonDeserializer - .commandFromApiJson(command.json()); - final List loanScheduleErrors = calculateLoanScheduleQuery.validate(); + this.fromApiJsonDeserializer.validateForModify(command.json()); - final LoanApplicationCommand loanApplicationCommand = this.fromApiJsonDeserializer.commandFromApiJson(command.json()); - final List loanApplicationErrors = loanApplicationCommand.validate(); + final Loan existingLoanApplication = retrieveLoanBy(loanId); - final List combinedErrors = new ArrayList(loanScheduleErrors); - combinedErrors.addAll(loanApplicationErrors); + final LoanChargeCommand[] charges = this.fromApiJsonDeserializer.extractLoanCharges(command.json()); - if (!combinedErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", - "Validation errors exist.", combinedErrors); } + final Map changes = existingLoanApplication.loanApplicationModification(command, charges, this.aprCalculator); - final Loan existingLoanApplication = retrieveLoanBy(loanId); + final String clientIdParamName = "clientId"; + if (changes.containsKey(clientIdParamName)) { + final Long clientId = command.longValueOfParameterNamed(clientIdParamName); + final Client client = this.clientRepository.findOne(clientId); + if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); } - final Map changes = existingLoanApplication.modifyLoanApplication(command, loanApplicationCommand.getCharges(), - this.aprCalculator); + existingLoanApplication.updateClient(client); + } - final String clientIdParamName = "clientId"; - if (changes.containsKey(clientIdParamName)) { - final Long clientId = command.longValueOfParameterNamed(clientIdParamName); - final Client client = this.clientRepository.findOne(clientId); - if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); } + final String productIdParamName = "productId"; + if (changes.containsKey(productIdParamName)) { + final Long productId = command.longValueOfParameterNamed(productIdParamName); + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } - existingLoanApplication.updateClient(client); + existingLoanApplication.updateLoanProduct(loanProduct); + } + + final String fundIdParamName = "fundId"; + if (changes.containsKey(fundIdParamName)) { + final Long fundId = command.longValueOfParameterNamed(fundIdParamName); + final Fund fund = this.loanAssembler.findFundByIdIfProvided(fundId); + + existingLoanApplication.updateFund(fund); + } + + final String strategyIdParamName = "transactionProcessingStrategyId"; + if (changes.containsKey(strategyIdParamName)) { + final Long strategyId = command.longValueOfParameterNamed(strategyIdParamName); + final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(strategyId); + + existingLoanApplication.updateTransactionProcessingStrategy(strategy); + } + + final String chargesParamName = "charges"; + if (changes.containsKey(chargesParamName)) { + final Set loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson()); + existingLoanApplication.updateLoanCharges(loanCharges); + } + + if (changes.containsKey("recalculateLoanSchedule")) { + changes.remove("recalculateLoanSchedule"); + + final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json()); + final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); + + final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query); + existingLoanApplication.updateLoanSchedule(loanSchedule); + existingLoanApplication.updateLoanScheduleDependentDerivedFields(); + } + + this.loanRepository.saveAndFlush(existingLoanApplication); + + final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); + if (StringUtils.isNotBlank(submittedOnNote)) { + Note note = Note.loanNote(existingLoanApplication, submittedOnNote); + this.noteRepository.save(note); + } + + return new CommandProcessingResultBuilder() // + .withEntityId(loanId) // + .withOfficeId(existingLoanApplication.getOfficeId()) // + .withClientId(existingLoanApplication.getClientId()) // + .withGroupId(existingLoanApplication.getGroupId()) // + .withLoanId(existingLoanApplication.getId()) // + .with(changes).build(); + } catch (DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + /* + * Guaranteed to throw an exception no matter what the data integrity issue + * is. + */ + private void handleDataIntegrityIssues(final JsonCommand command, final DataIntegrityViolationException dve) { + + Throwable realCause = dve.getMostSpecificCause(); + if (realCause.getMessage().contains("loan_account_no_UNIQUE")) { + + final String accountNo = command.stringValueOfParameterNamed("accountNo"); + throw new PlatformDataIntegrityException("error.msg.loan.duplicate.accountNo", "Loan with accountNo `" + accountNo + + "` already exists", "accountNo", accountNo); + } else if (realCause.getMessage().contains("loan_externalid_UNIQUE")) { + + final String externalId = command.stringValueOfParameterNamed("externalId"); + throw new PlatformDataIntegrityException("error.msg.loan.duplicate.externalId", "Loan with externalId `" + externalId + + "` already exists", "externalId", externalId); } - final String productIdParamName = "productId"; - if (changes.containsKey(productIdParamName)) { - final Long productId = command.longValueOfParameterNamed(productIdParamName); - final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); - if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } - - existingLoanApplication.updateLoanProduct(loanProduct); - } - - final String fundIdParamName = "fundId"; - if (changes.containsKey(fundIdParamName)) { - final Long fundId = command.longValueOfParameterNamed(fundIdParamName); - final Fund fund = this.loanAssembler.findFundByIdIfProvided(fundId); - - existingLoanApplication.updateFund(fund); - } - - final String strategyIdParamName = "transactionProcessingStrategyId"; - if (changes.containsKey(strategyIdParamName)) { - final Long strategyId = command.longValueOfParameterNamed(strategyIdParamName); - final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(strategyId); - - existingLoanApplication.updateTransactionProcessingStrategy(strategy); - } - - final String chargesParamName = "charges"; - if (changes.containsKey(chargesParamName)) { - final Set loanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson()); - existingLoanApplication.updateLoanCharges(loanCharges); - } - - if (changes.containsKey("recalculateLoanSchedule")) { - changes.remove("recalculateLoanSchedule"); - - final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json()); - final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper); - - final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query); - existingLoanApplication.updateLoanSchedule(loanSchedule); - existingLoanApplication.updateLoanScheduleDependentDerivedFields(); - } - - this.loanRepository.save(existingLoanApplication); - - final String submittedOnNote = command.stringValueOfParameterNamed("submittedOnNote"); - if (StringUtils.isNotBlank(submittedOnNote)) { - Note note = Note.loanNote(existingLoanApplication, submittedOnNote); - this.noteRepository.save(note); - } - - return new CommandProcessingResultBuilder() // - .withEntityId(loanId) // - .withOfficeId(existingLoanApplication.getOfficeId()) // - .withClientId(existingLoanApplication.getClientId()) // - .withGroupId(existingLoanApplication.getGroupId()) // - .withLoanId(existingLoanApplication.getId()) // - .with(changes).build(); + logAsErrorUnexpectedDataIntegrityException(dve); + throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue", + "Unknown data integrity issue with resource."); + } + + private void logAsErrorUnexpectedDataIntegrityException(final DataIntegrityViolationException dve) { + logger.error(dve.getMessage(), dve); } @Transactional diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index 2eb302c18..fcce0e3c1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -81,6 +81,7 @@ public class LoanAssembler { private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId) { + final String accountNo = fromApiJsonHelper.extractStringNamed("accountNo", element); final Long productId = fromApiJsonHelper.extractLongNamed("productId", element); final Long fundId = fromApiJsonHelper.extractLongNamed("fundId", element); final Long loanOfficerId = fromApiJsonHelper.extractLongNamed("loanOfficerId", element); @@ -96,7 +97,7 @@ public class LoanAssembler { final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element); final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); - LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(element, inArrearsTolerance); + final LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(element, inArrearsTolerance); Loan loanApplication = null; Client client = null; @@ -105,7 +106,7 @@ public class LoanAssembler { client = this.clientRepository.findOne(clientId); if (client == null || client.isDeleted()) { throw new ClientNotFoundException(clientId); } - loanApplication = Loan.newIndividualLoanApplication(client, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, + loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanSchedule, loanCharges); } @@ -113,7 +114,7 @@ public class LoanAssembler { group = this.groupRepository.findOne(groupId); if (group == null || group.isDeleted()) { throw new GroupNotFoundException(groupId); } - loanApplication = Loan.newGroupLoanApplication(group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, + loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanSchedule, loanCharges); } @@ -121,7 +122,7 @@ public class LoanAssembler { if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); } - loanApplication = Loan.newIndividualLoanApplicationFromGroup(client, group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, + loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanSchedule, loanCharges); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java deleted file mode 100644 index 6ba2b9909..000000000 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanProductRelatedDetailAssembler.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.mifosplatform.portfolio.loanaccount.service; - -import java.math.BigDecimal; - -import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.query.CalculateLoanScheduleQuery; -import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; -import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType; -import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class LoanProductRelatedDetailAssembler { - - private final LoanProductRepository loanProductRepository; - private final AprCalculator aprCalculator; - - @Autowired - public LoanProductRelatedDetailAssembler(final LoanProductRepository loanProductRepository, final AprCalculator aprCalculator) { - this.loanProductRepository = loanProductRepository; - this.aprCalculator = aprCalculator; - } - - public LoanProductRelatedDetail assembleFrom(final CalculateLoanScheduleQuery command) { - - final BigDecimal principalAmount = command.getPrincipal(); - final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod(); - final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType()); - final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType()); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command - .getInterestCalculationPeriodType()); - - final Integer defaultNumberOfInstallments = command.getNumberOfRepayments(); - final Integer repayEvery = command.getRepaymentEvery(); - final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType()); - - final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType()); - - final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, - defaultNominalInterestRatePerPeriod); - - final LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId()); - if (loanProduct == null) { throw new LoanProductNotFoundException(command.getProductId()); } - - final MonetaryCurrency currency = loanProduct.getCurrency(); - - // in arrerars tolerance isnt relevant when auto-calculating a loan - // schedule - final BigDecimal inArrearsTolerance = BigDecimal.ZERO; - - return new LoanProductRelatedDetail(currency, principalAmount, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, - defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, - defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 0a191db12..7dc6fa151 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -368,7 +368,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private static final class LoanMapper implements RowMapper { public String loanSchema() { - return "l.id as id, l.external_id as externalId, l.fund_id as fundId, f.name as fundName, " + return "l.id as id, l.account_no as accountNo, l.external_id as externalId, l.fund_id as fundId, f.name as fundName, " + " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription, c.id as clientId, c.display_name as clientName, " + " c.office_id as clientOfficeId, g.id as groupId, g.name as groupName, g.office_id as groupOfficeId," + " l.submittedon_date as submittedOnDate," @@ -391,75 +391,75 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public LoanBasicDetailsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - String currencyCode = rs.getString("currencyCode"); - String currencyName = rs.getString("currencyName"); - String currencyNameCode = rs.getString("currencyNameCode"); - String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, + 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 currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); + final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, currencyDisplaySymbol, currencyNameCode); - Long id = rs.getLong("id"); - String externalId = rs.getString("externalId"); + final Long id = rs.getLong("id"); + final String accountNo = rs.getString("accountNo"); + final String externalId = rs.getString("externalId"); - Long clientId = JdbcSupport.getLong(rs, "clientId"); - Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId"); - ; - String clientName = rs.getString("clientName"); + final Long clientId = JdbcSupport.getLong(rs, "clientId"); + final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId"); + final String clientName = rs.getString("clientName"); - Long groupId = JdbcSupport.getLong(rs, "groupId"); - Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId"); - ; - String groupName = rs.getString("groupName"); + final Long groupId = JdbcSupport.getLong(rs, "groupId"); + final Long groupOfficeId = JdbcSupport.getLong(rs, "groupOfficeId"); + final String groupName = rs.getString("groupName"); - Long fundId = JdbcSupport.getLong(rs, "fundId"); - String fundName = rs.getString("fundName"); - Long loanOfficerId = JdbcSupport.getLong(rs, "loanOfficerId"); - String loanOfficerName = rs.getString("loanOfficerName"); - Long loanProductId = JdbcSupport.getLong(rs, "loanProductId"); - String loanProductName = rs.getString("loanProductName"); - String loanProductDescription = rs.getString("loanProductDescription"); + final Long fundId = JdbcSupport.getLong(rs, "fundId"); + final String fundName = rs.getString("fundName"); + final Long loanOfficerId = JdbcSupport.getLong(rs, "loanOfficerId"); + final String loanOfficerName = rs.getString("loanOfficerName"); + final Long loanProductId = JdbcSupport.getLong(rs, "loanProductId"); + final String loanProductName = rs.getString("loanProductName"); + final String loanProductDescription = rs.getString("loanProductDescription"); - LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); - LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); - LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate"); - LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate"); - LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate"); - LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate"); - LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); - LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate"); + final LocalDate expectedDisbursementDate = JdbcSupport.getLocalDate(rs, "expectedDisbursementDate"); + final LocalDate actualDisbursementDate = JdbcSupport.getLocalDate(rs, "actualDisbursementDate"); + final LocalDate expectedFirstRepaymentOnDate = JdbcSupport.getLocalDate(rs, "expectedFirstRepaymentOnDate"); + final LocalDate interestChargedFromDate = JdbcSupport.getLocalDate(rs, "interestChargedFromDate"); + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); - BigDecimal principal = rs.getBigDecimal("principal"); - BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance"); - BigDecimal totalDisbursementCharges = rs.getBigDecimal("totalDisbursementCharges"); + final BigDecimal principal = rs.getBigDecimal("principal"); + final BigDecimal inArrearsTolerance = rs.getBigDecimal("inArrearsTolerance"); + final BigDecimal totalDisbursementCharges = rs.getBigDecimal("totalDisbursementCharges"); - Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments"); - Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery"); - BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); - BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); + final Integer numberOfRepayments = JdbcSupport.getInteger(rs, "numberOfRepayments"); + final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery"); + final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); + final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); - Integer termFrequency = JdbcSupport.getInteger(rs, "termFrequency"); - Integer termPeriodFrequencyTypeInt = JdbcSupport.getInteger(rs, "termPeriodFrequencyType"); - EnumOptionData termPeriodFrequencyType = LoanEnumerations.termFrequencyType(termPeriodFrequencyTypeInt); + final Integer termFrequency = JdbcSupport.getInteger(rs, "termFrequency"); + final Integer termPeriodFrequencyTypeInt = JdbcSupport.getInteger(rs, "termPeriodFrequencyType"); + final EnumOptionData termPeriodFrequencyType = LoanEnumerations.termFrequencyType(termPeriodFrequencyTypeInt); - int repaymentFrequencyTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyType"); - EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeInt); + final int repaymentFrequencyTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyType"); + final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeInt); - int interestRateFrequencyTypeInt = JdbcSupport.getInteger(rs, "interestRateFrequencyType"); - EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeInt); + final int interestRateFrequencyTypeInt = JdbcSupport.getInteger(rs, "interestRateFrequencyType"); + final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeInt); - Integer transactionStrategyId = JdbcSupport.getInteger(rs, "transactionStrategyId"); + final Integer transactionStrategyId = JdbcSupport.getInteger(rs, "transactionStrategyId"); - int amortizationTypeInt = JdbcSupport.getInteger(rs, "amortizationType"); - int interestTypeInt = JdbcSupport.getInteger(rs, "interestType"); - int interestCalculationPeriodTypeInt = JdbcSupport.getInteger(rs, "interestCalculationPeriodType"); + final int amortizationTypeInt = JdbcSupport.getInteger(rs, "amortizationType"); + final int interestTypeInt = JdbcSupport.getInteger(rs, "interestType"); + final int interestCalculationPeriodTypeInt = JdbcSupport.getInteger(rs, "interestCalculationPeriodType"); - EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeInt); - EnumOptionData interestType = LoanEnumerations.interestType(interestTypeInt); - EnumOptionData interestCalculationPeriodType = LoanEnumerations.interestCalculationPeriodType(interestCalculationPeriodTypeInt); + final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeInt); + final EnumOptionData interestType = LoanEnumerations.interestType(interestTypeInt); + final EnumOptionData interestCalculationPeriodType = LoanEnumerations + .interestCalculationPeriodType(interestCalculationPeriodTypeInt); - Integer lifeCycleStatusId = JdbcSupport.getInteger(rs, "lifeCycleStatusId"); - EnumOptionData status = LoanEnumerations.status(lifeCycleStatusId); + final Integer lifeCycleStatusId = JdbcSupport.getInteger(rs, "lifeCycleStatusId"); + final EnumOptionData status = LoanEnumerations.status(lifeCycleStatusId); LocalDate lifeCycleStatusDate = submittedOnDate; if (approvedOnDate != null) { @@ -473,8 +473,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } Collection charges = null; - return new LoanBasicDetailsData(id, externalId, clientId, clientName, clientOfficeId, groupId, groupName, groupOfficeId, - loanProductId, loanProductName, loanProductDescription, fundId, fundName, closedOnDate, submittedOnDate, + return new LoanBasicDetailsData(id, accountNo, externalId, clientId, clientName, clientOfficeId, groupId, groupName, + groupOfficeId, loanProductId, loanProductName, loanProductDescription, fundId, fundName, closedOnDate, submittedOnDate, approvedOnDate, expectedDisbursementDate, actualDisbursementDate, expectedMaturityDate, expectedFirstRepaymentOnDate, interestChargedFromDate, currencyData, principal, inArrearsTolerance, numberOfRepayments, repaymentEvery, interestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,