MIFOSX-172: WIP support loan writeoff, closure scenarios with accounting.

This commit is contained in:
Keith Woodlock
2013-02-19 12:59:51 +00:00
parent 87eb0a6fe8
commit 524206f0ff
25 changed files with 240 additions and 372 deletions
@@ -7,7 +7,6 @@ package org.mifosplatform.accounting.journalentry.service;
import java.util.Map; import java.util.Map;
import org.mifosplatform.accounting.glaccount.data.LoanDTO;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
@@ -18,7 +17,4 @@ public interface JournalEntryWritePlatformService {
CommandProcessingResult revertJournalEntry(JsonCommand command); CommandProcessingResult revertJournalEntry(JsonCommand command);
void createJournalEntriesForLoan(Map<String, Object> accountingBridgeData); void createJournalEntriesForLoan(Map<String, Object> accountingBridgeData);
void createJournalEntriesForLoan(LoanDTO loanDTO);
} }
@@ -140,17 +140,6 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build(); return new CommandProcessingResultBuilder().withTransactionId(reversalTransactionId).build();
} }
@Transactional
@Override
public void createJournalEntriesForLoan(final LoanDTO loanDTO) {
// shouldn't be before an accounting closure
if (loanDTO.isCashBasedAccountingEnabled()) {
createJournalEntriesUsingCashRules(loanDTO);
} else if (loanDTO.isAccrualBasedAccountingEnabled()) {
// TODO Vishwas: add accrual based accounting rules
}
}
@Transactional @Transactional
@Override @Override
public void createJournalEntriesForLoan(final Map<String, Object> accountingBridgeData) { public void createJournalEntriesForLoan(final Map<String, Object> accountingBridgeData) {
@@ -33,10 +33,11 @@ public class ApiRequestJsonSerializationSettings {
public static ApiRequestJsonSerializationSettings from(final boolean prettyPrint, final Set<String> parametersForPartialResponse, public static ApiRequestJsonSerializationSettings from(final boolean prettyPrint, final Set<String> parametersForPartialResponse,
final boolean template, final Long commandId, final boolean makerCheckerable, final boolean includeJson) { final boolean template, final Long commandId, final boolean makerCheckerable, final boolean includeJson) {
// FIXME - kw - rather than always creating new objects for this could // FIXME - KW - rather than always creating new objects for this could
// just send by common ones like, prettyprint=false, empty response // just send by common ones like, prettyprint=false, empty response
// parameters // parameters
return new ApiRequestJsonSerializationSettings(prettyPrint, parametersForPartialResponse, template, commandId, makerCheckerable, includeJson); return new ApiRequestJsonSerializationSettings(prettyPrint, parametersForPartialResponse, template, commandId, makerCheckerable,
includeJson);
} }
public boolean isPrettyPrint() { public boolean isPrettyPrint() {
@@ -27,7 +27,7 @@ import org.springframework.stereotype.Service;
import com.sun.rowset.CachedRowSetImpl; import com.sun.rowset.CachedRowSetImpl;
//TODO - Performance Item - most items (code values etc) can be cached but not doing that yet //TODO - JW - Performance Item - most items (code values etc) can be cached but not doing that yet
@Service @Service
public class GenericDataServiceImpl implements GenericDataService { public class GenericDataServiceImpl implements GenericDataService {
@@ -41,8 +41,8 @@ public class GenericDataServiceImpl implements GenericDataService {
@Override @Override
public CachedRowSet getCachedResultSet(final String sql, final String sqlErrorMsg) { public CachedRowSet getCachedResultSet(final String sql, final String sqlErrorMsg) {
// FIXME - Need to reimplement this away from Sun library - could be // FIXME - KW - Need to reimplement this away from Sun library - could
// mixture of Lists and jdbcTemplate.query // be mixture of Lists and jdbcTemplate.query
// long startTime = System.currentTimeMillis(); // long startTime = System.currentTimeMillis();
Connection db_connection = null; Connection db_connection = null;
@@ -195,7 +195,7 @@ public class GenericDataServiceImpl implements GenericDataService {
writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth() writer.append("[" + localDate.getYear() + ", " + localDate.getMonthOfYear() + ", " + localDate.getDayOfMonth()
+ "]"); + "]");
} else } else
writer.append(doubleQuote + replace(currVal, doubleQuote,slashDoubleQuote) + doubleQuote); writer.append(doubleQuote + replace(currVal, doubleQuote, slashDoubleQuote) + doubleQuote);
} }
} else } else
writer.append("null"); writer.append("null");
@@ -82,7 +82,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
return new StreamingOutput() { return new StreamingOutput() {
@Override @Override
public void write(OutputStream out) { public void write(final OutputStream out) {
try { try {
GenericResultsetData result = retrieveGenericResultset(name, type, queryParams); GenericResultsetData result = retrieveGenericResultset(name, type, queryParams);
@@ -108,7 +108,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
} }
private static StringBuffer generateCsvFileBuffer(GenericResultsetData result) { private static StringBuffer generateCsvFileBuffer(final GenericResultsetData result) {
StringBuffer writer = new StringBuffer(); StringBuffer writer = new StringBuffer();
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders(); List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders();
@@ -206,7 +206,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
} }
private String getSql(String name, String type) { private String getSql(final String name, final String type) {
String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'"; String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'";
inputSql = genericDataService.wrapSQL(inputSql); inputSql = genericDataService.wrapSQL(inputSql);
@@ -226,7 +226,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
} }
@Override @Override
public String getReportType(String reportName) { public String getReportType(final String reportName) {
String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'"; String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'";
sql = genericDataService.wrapSQL(sql); sql = genericDataService.wrapSQL(sql);
@@ -246,7 +246,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
} }
@Override @Override
public Response processPentahoRequest(String reportName, String outputTypeParam, Map<String, String> queryParams) { public Response processPentahoRequest(final String reportName, final String outputTypeParam, final Map<String, String> queryParams) {
String outputType = "HTML"; String outputType = "HTML";
if (StringUtils.isNotBlank(outputTypeParam)) outputType = outputTypeParam; if (StringUtils.isNotBlank(outputTypeParam)) outputType = outputTypeParam;
@@ -258,7 +258,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
if (noPentaho) if (noPentaho)
throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled", "Pentaho is not enabled"); throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled", "Pentaho is not enabled");
// TODO - use pentaho location finder like Pawel does in Mifos // TODO - JW - use pentaho location finder like Pawel does in Mifos
// String reportPath = // String reportPath =
// "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\" // "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\"
// + reportName + ".prpt"; // + reportName + ".prpt";
@@ -313,7 +313,7 @@ public class ReadReportingServiceImpl implements ReadReportingService {
} }
private void addParametersToReport(MasterReport report, Map<String, String> queryParams) { private void addParametersToReport(final MasterReport report, final Map<String, String> queryParams) {
try { try {
ReportParameterValues rptParamValues = report.getParameterValues(); ReportParameterValues rptParamValues = report.getParameterValues();
@@ -103,7 +103,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'"; String updatePermissionChecker = "'UPDATE_" + datatable + "_CHECKER'";
String deletePermission = "'DELETE_" + datatable + "'"; String deletePermission = "'DELETE_" + datatable + "'";
String deletePermissionChecker = "'DELETE_" + datatable + "_CHECKER'"; String deletePermissionChecker = "'DELETE_" + datatable + "_CHECKER'";
// TODO - put in batch command later // TODO - JW - put in batch command later
String sql = "insert into x_registered_table (registered_table_name, application_table_name) values ('" + datatable + "', '" String sql = "insert into x_registered_table (registered_table_name, application_table_name) values ('" + datatable + "', '"
+ appTable + "')"; + appTable + "')";
@@ -125,7 +125,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
@Override @Override
public void deregisterDatatable(final String datatable) { public void deregisterDatatable(final String datatable) {
// TODO - put in batch command later // TODO - JW - put in batch command later
String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_" String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_"
+ datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')"; + datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')";
@@ -356,7 +356,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ
} }
private String getApplicationTableName(final String datatable) { private String getApplicationTableName(final String datatable) {
// TODO - only used for update... can probably remove this after as its // TODO - JW - only used for update... can probably remove this after as
// its
// a reread // a reread
String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'"; String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'";
@@ -37,7 +37,7 @@ import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart; import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam; import com.sun.jersey.multipart.FormDataParam;
// FIXME - kw- image should be images to keep consitent with rest of API - even if we only support one image per client. // FIXME - KW - image should be images to keep consistent with rest of API - even if we only support one image per client.
@Path("/clients/{clientId}/image") @Path("/clients/{clientId}/image")
@Component @Component
@Scope("singleton") @Scope("singleton")
@@ -63,9 +63,9 @@ public class ClientImagesApiResource {
@POST @POST
@Consumes({ MediaType.MULTIPART_FORM_DATA }) @Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String addNewClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") Long fileSize, public String addNewClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") InputStream inputStream, @FormDataParam("file") FormDataContentDisposition fileDetails, @FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") FormDataBodyPart bodyPart) { @FormDataParam("file") final FormDataBodyPart bodyPart) {
// TODO: vishwas might need more advances validation (like reading magic // TODO: vishwas might need more advances validation (like reading magic
// number) for handling malicious clients // number) for handling malicious clients
@@ -119,9 +119,9 @@ public class ClientImagesApiResource {
@PUT @PUT
@Consumes({ MediaType.MULTIPART_FORM_DATA }) @Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public String updateClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") Long fileSize, public String updateClientImage(@PathParam("clientId") final Long clientId, @HeaderParam("Content-Length") final Long fileSize,
@FormDataParam("file") InputStream inputStream, @FormDataParam("file") FormDataContentDisposition fileDetails, @FormDataParam("file") final InputStream inputStream, @FormDataParam("file") final FormDataContentDisposition fileDetails,
@FormDataParam("file") FormDataBodyPart bodyPart) { @FormDataParam("file") final FormDataBodyPart bodyPart) {
return addNewClientImage(clientId, fileSize, inputStream, fileDetails, bodyPart); return addNewClientImage(clientId, fileSize, inputStream, fileDetails, bodyPart);
} }
@@ -58,10 +58,10 @@ public class ClientIdentifierCommand {
baseDataValidator.reset().parameter("documentKey").value(this.documentKey).ignoreIfNull().notBlank(); baseDataValidator.reset().parameter("documentKey").value(this.documentKey).ignoreIfNull().notBlank();
// FIXME - kw // FIXME - KW - add in validation
// if (command.isDocumentTypeChanged()) { // if (command.isDocumentTypeChanged()) {
// baseDataValidator.reset().parameter("documentTypeId").value(command.getDocumentTypeId()).notNull().integerGreaterThanZero(); // baseDataValidator.reset().parameter("documentTypeId").value(command.getDocumentTypeId()).notNull().integerGreaterThanZero();
// } // }
baseDataValidator.reset().anyOfNotNull(this.documentTypeId, this.documentKey); baseDataValidator.reset().anyOfNotNull(this.documentTypeId, this.documentKey);
@@ -96,7 +96,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
} }
@Override @Override
public Collection<ClientLookup> retrieveAllIndividualClientsForLookup(String extraCriteria) { public Collection<ClientLookup> retrieveAllIndividualClientsForLookup(final String extraCriteria) {
this.context.authenticatedUser(); this.context.authenticatedUser();
@@ -112,7 +112,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
} }
@Override @Override
public Collection<ClientLookup> retrieveAllIndividualClientsForLookupByOfficeId(Long officeId) { public Collection<ClientLookup> retrieveAllIndividualClientsForLookupByOfficeId(final Long officeId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
ClientLookupMapper rm = new ClientLookupMapper(); ClientLookupMapper rm = new ClientLookupMapper();
@@ -290,7 +290,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
} }
@Override @Override
public Collection<ClientAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(Long clientId, Long loanOfficerId) { public Collection<ClientAccountSummaryData> retrieveClientLoanAccountsByLoanOfficerId(final Long clientId, final Long loanOfficerId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
@@ -357,7 +357,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
} }
@Override @Override
public NoteData retrieveClientNote(Long clientId, Long noteId) { public NoteData retrieveClientNote(final Long clientId, final Long noteId) {
try { try {
context.authenticatedUser(); context.authenticatedUser();
@@ -365,7 +365,8 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
// Check if client exists // Check if client exists
retrieveIndividualClient(clientId); retrieveIndividualClient(clientId);
// FIXME - use join on sql query to fetch user information for note // FIXME - KW - use join on sql query to fetch user information for
// note
// rather than fetching users? // rather than fetching users?
Collection<AppUserData> allUsers = this.appUserReadPlatformService.retrieveAllUsers(); Collection<AppUserData> allUsers = this.appUserReadPlatformService.retrieveAllUsers();
@@ -380,14 +381,14 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
} }
@Override @Override
public Collection<NoteData> retrieveAllClientNotes(Long clientId) { public Collection<NoteData> retrieveAllClientNotes(final Long clientId) {
context.authenticatedUser(); context.authenticatedUser();
// Check if client exists // Check if client exists
retrieveIndividualClient(clientId); retrieveIndividualClient(clientId);
// FIXME - use join on sql query to fetch user information for note // FIXME - KW - use join on sql query to fetch user information for note
// rather than fetching users? // rather than fetching users?
Collection<AppUserData> allUsers = this.appUserReadPlatformService.retrieveAllUsers(); Collection<AppUserData> allUsers = this.appUserReadPlatformService.retrieveAllUsers();
@@ -402,7 +403,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService
private final Collection<AppUserData> allUsers; private final Collection<AppUserData> allUsers;
public NoteMapper(Collection<AppUserData> allUsers) { public NoteMapper(final Collection<AppUserData> allUsers) {
this.allUsers = allUsers; this.allUsers = allUsers;
} }
@@ -36,12 +36,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
// FIXME: - undo disbursal fails when transactions have notes against them
// FIXME: - modifying loan after disbursement
// FIXME: - does not take into account 'actual disbursement' date being different to 'expected'
// FIXME: - the schedule is 'generated' rather than been read from current loan schedule
// FIXME: - should not allow modify loan after disbursal (should be only restructure/reschedule)
// FIXME: - ui problem with default date for disburse dialog on some screens
@Path("/loans/{loanId}/transactions") @Path("/loans/{loanId}/transactions")
@Component @Component
@Scope("singleton") @Scope("singleton")
@@ -1,57 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.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;
/**
* Immutable command for loan transactions.
*/
public class LoanTransactionCommand {
private final LocalDate transactionDate;
private final BigDecimal transactionAmount;
private final String note;
public LoanTransactionCommand(final LocalDate paymentDate, final BigDecimal paymentAmount, final String note) {
this.transactionDate = paymentDate;
this.transactionAmount = paymentAmount;
this.note = note;
}
public void validate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("transactionDate").value(this.transactionDate).notNull();
baseDataValidator.reset().parameter("transactionAmount").value(this.transactionAmount).notNull().positiveAmount();
baseDataValidator.reset().parameter("note").value(this.note).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateNonMonetaryTransaction() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
baseDataValidator.reset().parameter("transactionDate").value(this.transactionDate).notNull();
baseDataValidator.reset().parameter("note").value(this.note).notExceedingLengthOf(1000);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -144,7 +144,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
} }
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(Set<LoanCharge> charges) { private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges) {
LoanCharge earliestUnpaidCharge = null; LoanCharge earliestUnpaidCharge = null;
for (LoanCharge loanCharge : charges) { for (LoanCharge loanCharge : charges) {
@@ -175,6 +175,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (currentInstallment.isNotFullyCompleted()) { if (currentInstallment.isNotFullyCompleted()) {
principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(currency)); principalPortion = principalPortion.plus(currentInstallment.writeOffOutstandingPrincipal(currency));
interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(currency)); interestPortion = interestPortion.plus(currentInstallment.writeOffOutstandingInterest(currency));
feeChargesPortion = feeChargesPortion.plus(currentInstallment.writeOffOutstandingFeeCharges(currency));
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.writeOffOutstandingPenaltyCharges(currency));
} }
} }
@@ -387,8 +387,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
// TODO - only need to reprocess transactions against loan schedule
// if loan charge is added with due date before latest transaction.
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
@@ -1351,7 +1349,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
loanTransactionDate, this.getDisbursementDate()); loanTransactionDate, this.getDisbursementDate());
} }
if (loanTransactionDate.isAfter(new LocalDate())) { if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) {
final String errorMessage = "The transaction date cannot be in the future."; final String errorMessage = "The transaction date cannot be in the future.";
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate);
} }
@@ -1359,12 +1357,12 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (loanTransaction.isInterestWaiver()) { if (loanTransaction.isInterestWaiver()) {
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan(); Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
if (adjustedTransaction != null) { if (adjustedTransaction != null) {
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount()); totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(loanCurrency()));
} }
if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) { if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan."; final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage, throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage,
loanTransaction.getAmount(), totalInterestOutstandingOnLoan.getAmount()); loanTransaction.getAmount(loanCurrency()), totalInterestOutstandingOnLoan.getAmount());
} }
} }
@@ -1616,53 +1614,61 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public LoanTransaction closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes) { final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING,
LoanStatus.fromInt(this.loanStatus)); LoanStatus.fromInt(this.loanStatus));
LoanTransaction loanTransaction = null;
if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) {
this.loanStatus = statusEnum.getValue(); this.loanStatus = statusEnum.getValue();
changes.put("status", LoanEnumerations.status(this.loanStatus)); changes.put("status", LoanEnumerations.status(this.loanStatus));
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed("transactionDate");
this.closedOnDate = writtenOffOnLocalDate.toDate();
this.writtenOffOnDate = writtenOffOnLocalDate.toDate();
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("writtenOffOnDate", command.stringValueOfParameterNamed("transactionDate"));
if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
+ getDisbursementDate().toString();
throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage,
writtenOffOnLocalDate, getDisbursementDate());
}
if (writtenOffOnLocalDate.isAfter(DateUtils.getLocalDateOfTenant())) {
final String errorMessage = "The date on which a loan is written off cannot be in the future.";
throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate);
}
loanTransaction = LoanTransaction.writeoff(this, writtenOffOnLocalDate);
final boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions);
if (!isLastTransaction) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage,
writtenOffOnLocalDate);
}
this.loanTransactions.add(loanTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor
.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
} }
final LocalDate writtenOffOnLocalDate = command.localDateValueOfParameterNamed("transactionDate");
this.closedOnDate = writtenOffOnLocalDate.toDate();
this.writtenOffOnDate = writtenOffOnLocalDate.toDate();
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("writtenOffOnDate", command.stringValueOfParameterNamed("transactionDate"));
if (writtenOffOnLocalDate.isBefore(this.getDisbursementDate())) {
final String errorMessage = "The date on which a loan is written off cannot be before the loan disbursement date: "
+ getDisbursementDate().toString();
throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.before.submittal.date", errorMessage,
writtenOffOnLocalDate, getDisbursementDate());
}
if (writtenOffOnLocalDate.isAfter(new LocalDate())) {
final String errorMessage = "The date on which a loan is written off cannot be in the future.";
throw new InvalidLoanStateTransitionException("writeoff", "cannot.be.a.future.date", errorMessage, writtenOffOnLocalDate);
}
final LoanTransaction loanTransaction = LoanTransaction.writeoff(this, writtenOffOnLocalDate);
boolean isLastTransaction = isChronologicallyLatestTransaction(loanTransaction, loanTransactions);
if (!isLastTransaction) {
final String errorMessage = "The date of the writeoff transaction must occur on or before previous transactions.";
throw new InvalidLoanStateTransitionException("writeoff", "must.occur.on.or.after.other.transaction.dates", errorMessage,
writtenOffOnLocalDate);
}
this.loanTransactions.add(loanTransaction);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessor
.determineProcessor(this.transactionProcessingStrategy);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), this.repaymentScheduleInstallments);
return loanTransaction; return loanTransaction;
} }
public LoanTransaction close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes) { final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate");
@@ -1676,7 +1682,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
getDisbursementDate()); getDisbursementDate());
} }
if (closureDate.isAfter(new LocalDate())) { if (closureDate.isAfter(DateUtils.getLocalDateOfTenant())) {
final String errorMessage = "The date on which a loan is closed cannot be in the future."; final String errorMessage = "The date on which a loan is closed cannot be in the future.";
throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate); throw new InvalidLoanStateTransitionException("close", "cannot.be.a.future.date", errorMessage, closureDate);
} }
@@ -1712,10 +1718,9 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (isOverPaid()) { if (isOverPaid()) {
final Money totalLoanOverpayment = calculateTotalOverpayment(); final Money totalLoanOverpayment = calculateTotalOverpayment();
// FIXME - kw - use overpaymentTolerance setting when in place on
// loan product settings.
if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) { if (totalLoanOverpayment.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
// TODO - technically should set somewhere that this loan has // TODO - KW - technically should set somewhere that this loan
// has
// 'overpaid' amount // 'overpaid' amount
updateLoanForClosure(closureDate, loanLifecycleStateMachine, changes); updateLoanForClosure(closureDate, loanLifecycleStateMachine, changes);
} else if (totalLoanOverpayment.isGreaterThanZero()) { } else if (totalLoanOverpayment.isGreaterThanZero()) {
@@ -1729,11 +1734,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
private Money getTotalOutstanding() { private Money getTotalOutstanding() {
Money totalOutstanding = Money.zero(loanCurrency()); Money totalOutstanding = Money.zero(loanCurrency());
// 1. check totalOutstanding value on loan
// 2. if null calculate from loan schedule
final LoanScheduleWrapper wrapper = new LoanScheduleWrapper(); final LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
totalOutstanding = wrapper.calculateTotalOutstanding(loanCurrency(), this.repaymentScheduleInstallments); totalOutstanding = wrapper.calculateTotalOutstanding(loanCurrency(), this.repaymentScheduleInstallments);
return totalOutstanding; return totalOutstanding;
} }
@@ -1751,8 +1755,6 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
/** /**
* Behaviour added to comply with capability of previous mifos product to * Behaviour added to comply with capability of previous mifos product to
* support easier transition to mifosx platform. * support easier transition to mifosx platform.
*
* @param changes
*/ */
public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public void closeAsMarkedForReschedule(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes) { final Map<String, Object> changes) {
@@ -2013,11 +2015,11 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
private Money getTotalPaidInRepayments() { private Money getTotalPaidInRepayments() {
Money cumulativePaid = Money.zero(this.loanRepaymentScheduleDetail.getPrincipal().getCurrency()); Money cumulativePaid = Money.zero(loanCurrency());
for (LoanTransaction repayment : this.loanTransactions) { for (LoanTransaction repayment : this.loanTransactions) {
if (repayment.isRepayment()) { if (repayment.isRepayment()) {
cumulativePaid = cumulativePaid.plus(repayment.getAmount()); cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
} }
} }
@@ -2209,20 +2211,15 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
return officeId; return officeId;
} }
public boolean isAccountingEnabledOnLoanProduct() { private Boolean isCashBasedAccountingEnabledOnLoanProduct() {
return this.loanProduct.isAccountingEnabled();
}
public Boolean isCashBasedAccountingEnabledOnLoanProduct() {
return this.loanProduct.isCashBasedAccountingEnabled(); return this.loanProduct.isCashBasedAccountingEnabled();
} }
public Boolean isAccrualBasedAccountingEnabledOnLoanProduct() { private Boolean isAccrualBasedAccountingEnabledOnLoanProduct() {
return this.loanProduct.isAccrualBasedAccountingEnabled(); return this.loanProduct.isAccrualBasedAccountingEnabled();
} }
// TODO - make private if possible private Long productId() {
public Long productId() {
return this.loanProduct.getId(); return this.loanProduct.getId();
} }
@@ -433,6 +433,22 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return interestDue; return interestDue;
} }
public Money writeOffOutstandingFeeCharges(final MonetaryCurrency currency) {
final Money feeChargesOutstanding = getFeeChargesOutstanding(currency);
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesOutstanding.getAmount());
this.completed = getTotalOutstanding(currency).isZero();
return feeChargesOutstanding;
}
public Money writeOffOutstandingPenaltyCharges(final MonetaryCurrency currency) {
final Money penaltyChargesOutstanding = getPenaltyChargesOutstanding(currency);
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesOutstanding.getAmount());
this.completed = getTotalOutstanding(currency).isZero();
return penaltyChargesOutstanding;
}
public boolean isOverdueOn(final LocalDate transactionDate) { public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(transactionDate); return this.getDueDate().isBefore(transactionDate);
} }
@@ -33,7 +33,7 @@ public class LoanScheduleWrapper {
} }
/** /**
* FIXME - this is not a side effect free approach so move this into a * FIXME - KW - this is not a side effect free approach so move this into a
* specific wrapper than makes this obvious from the read-only wrapper. * specific wrapper than makes this obvious from the read-only wrapper.
*/ */
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
@@ -50,16 +50,16 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
private BigDecimal amount; private BigDecimal amount;
@Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "principal_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal principalPortion = BigDecimal.ZERO; private BigDecimal principalPortion;
@Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "interest_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestPortion = BigDecimal.ZERO; private BigDecimal interestPortion;
@Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "fee_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesPortion = BigDecimal.ZERO; private BigDecimal feeChargesPortion;
@Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true) @Column(name = "penalty_charges_portion_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesPortion = BigDecimal.ZERO; private BigDecimal penaltyChargesPortion;
@Column(name = "is_reversed", nullable = false) @Column(name = "is_reversed", nullable = false)
private boolean reversed; private boolean reversed;
@@ -160,30 +160,14 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
return Money.of(currency, this.interestPortion); return Money.of(currency, this.interestPortion);
} }
public BigDecimal getInterestPortion() {
return this.interestPortion;
}
public Money getFeeChargesPortion(final MonetaryCurrency currency) { public Money getFeeChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesPortion); return Money.of(currency, this.feeChargesPortion);
} }
public BigDecimal getFeePortion() {
return this.feeChargesPortion;
}
public Money getPenaltyChargesPortion(final MonetaryCurrency currency) { public Money getPenaltyChargesPortion(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyChargesPortion); return Money.of(currency, this.penaltyChargesPortion);
} }
public BigDecimal getPenaltyChargesPortion() {
return this.penaltyChargesPortion;
}
public BigDecimal getAmount() {
return this.amount;
}
public Money getAmount(final MonetaryCurrency currency) { public Money getAmount(final MonetaryCurrency currency) {
return Money.of(currency, this.amount); return Money.of(currency, this.amount);
} }
@@ -89,7 +89,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull()
.inMinMaxRange(0, 3); .inMinMaxRange(0, 3);
// FIXME - this constraint doesnt really need to be here. should be // FIXME - KW - this constraint doesnt really need to be here. should be
// possible to express loan term as say 12 months whilst also saying // possible to express loan term as say 12 months whilst also saying
// - that the repayment structure is 6 repayments every bi-monthly. // - that the repayment structure is 6 repayments every bi-monthly.
validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType, numberOfRepayments, validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, loanTermFrequencyType, numberOfRepayments,
@@ -207,7 +207,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
} }
private void validateRepaymentsStartingFromDateAndInterestChargedFromDate(final List<ApiParameterError> dataValidationErrors, private void validateRepaymentsStartingFromDateAndInterestChargedFromDate(final List<ApiParameterError> dataValidationErrors,
final LocalDate expectedDisbursementDate, LocalDate repaymentsStartingFromDate, LocalDate interestChargedFromDate) { final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final LocalDate interestChargedFromDate) {
if (repaymentsStartingFromDate != null && interestChargedFromDate == null) { if (repaymentsStartingFromDate != null && interestChargedFromDate == null) {
ApiParameterError error = ApiParameterError.parameterError( ApiParameterError error = ApiParameterError.parameterError(
@@ -228,7 +228,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
} }
private void validateRepaymentsStartingFromDateIsAfterDisbursementDate(final List<ApiParameterError> dataValidationErrors, private void validateRepaymentsStartingFromDateIsAfterDisbursementDate(final List<ApiParameterError> dataValidationErrors,
final LocalDate expectedDisbursementDate, LocalDate repaymentsStartingFromDate) { final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate) {
if (expectedDisbursementDate != null) { if (expectedDisbursementDate != null) {
if (repaymentsStartingFromDate != null && expectedDisbursementDate.isAfter(repaymentsStartingFromDate)) { if (repaymentsStartingFromDate != null && expectedDisbursementDate.isAfter(repaymentsStartingFromDate)) {
ApiParameterError error = ApiParameterError.parameterError( ApiParameterError error = ApiParameterError.parameterError(
@@ -6,6 +6,7 @@
package org.mifosplatform.portfolio.loanaccount.serialization; package org.mifosplatform.portfolio.loanaccount.serialization;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@@ -36,6 +37,11 @@ public final class LoanEventApiJsonValidator {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
public void validateDisbursement(final String json) { public void validateDisbursement(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
@@ -59,8 +65,50 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors); throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) { public void validateTransaction(final String json) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note",
"locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateTransactionWithNoAmount(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Set<String> disbursementParameters = new HashSet<String>(Arrays.asList("transactionDate", "note", "locale", "dateFormat"));
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, disbursementParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
final String note = fromApiJsonHelper.extractStringNamed("note", element);
baseDataValidator.reset().parameter("note").value(note).notExceedingLengthOf(1000);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
} }
} }
@@ -1,63 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.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.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.LoanTransactionCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
/**
* Implementation of {@link FromApiJsonDeserializer} for
* {@link LoanTransactionCommand}'s.
*/
@Component
public final class LoanTransactionCommandFromApiJsonDeserializer extends AbstractFromApiJsonDeserializer<LoanTransactionCommand> {
/**
* The parameters supported for this command.
*/
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("transactionDate", "transactionAmount", "note", "dateFormat",
"locale" ,"status", "closedOnDate", "writtenOffOnDate", "rescheduledOnDate"));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public LoanTransactionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@Override
public LoanTransactionCommand commandFromApiJson(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = fromApiJsonHelper.parse(json);
final LocalDate transactionDate = fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
final BigDecimal transactionAmount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
final String note = fromApiJsonHelper.extractStringNamed("note", element);
return new LoanTransactionCommand(transactionDate, transactionAmount, note);
}
}
@@ -248,7 +248,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
context.authenticatedUser(); context.authenticatedUser();
// TODO - OPTIMIZE - write simple sql query to fetch back date of // TODO - KW - OPTIMIZE - write simple sql query to fetch back date of
// possible next transaction date. // possible next transaction date.
Loan loan = this.loanRepository.findOne(loanId); Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { throw new LoanNotFoundException(loanId); } if (loan == null) { throw new LoanNotFoundException(loanId); }
@@ -273,7 +273,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
context.authenticatedUser(); context.authenticatedUser();
// TODO - OPTIMIZE - write simple sql query to fetch back overdue // TODO - KW -OPTIMIZE - write simple sql query to fetch back overdue
// interest that can be waived along with the date of repayment period // interest that can be waived along with the date of repayment period
// interest is overdue. // interest is overdue.
final Loan loan = this.loanRepository.findOne(loanId); final Loan loan = this.loanRepository.findOne(loanId);
@@ -290,8 +290,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WAIVE_INTEREST);
return new LoanTransactionData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(), final MonetaryCurrency monetaryCurrency = new MonetaryCurrency(currency.getCode(), currency.getDecimalPlaces());
waiveOfInterest.getAmount(), null, null, null, null); final BigDecimal amount = waiveOfInterest.getAmount(monetaryCurrency).getAmount();
return new LoanTransactionData(null, transactionType, currencyData, waiveOfInterest.getTransactionDate(), amount, null, null, null,
null);
} }
@Override @Override
@@ -299,8 +301,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
context.authenticatedUser(); context.authenticatedUser();
LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.WRITEOFF);
return new LoanTransactionData(null, transactionType, null, new LocalDate(), null, null, null, null, null); return new LoanTransactionData(null, transactionType, null, DateUtils.getLocalDateOfTenant(), null, null, null, null, null);
} }
@Override @Override
@@ -14,14 +14,10 @@ import java.util.Map;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.accounting.glaccount.data.LoanDTO;
import org.mifosplatform.accounting.glaccount.data.LoanTransactionDTO;
import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.exception.NoAuthorizationException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepository;
@@ -34,7 +30,6 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Note; import org.mifosplatform.portfolio.client.domain.Note;
import org.mifosplatform.portfolio.client.domain.NoteRepository; import org.mifosplatform.portfolio.client.domain.NoteRepository;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanTransactionCommand;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
@@ -51,7 +46,6 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignment
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanChargeCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanChargeCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanTransactionCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.mifosplatform.useradministration.domain.AppUser; import org.mifosplatform.useradministration.domain.AppUser;
@@ -64,7 +58,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final LoanEventApiJsonValidator loanEventApiJsonValidator; private final LoanEventApiJsonValidator loanEventApiJsonValidator;
private final LoanTransactionCommandFromApiJsonDeserializer loanTransactionCommandFromApiJsonDeserializer;
private final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer; private final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer;
private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer; private final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
@@ -79,7 +72,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final LoanEventApiJsonValidator loanEventApiJsonValidator, final LoanEventApiJsonValidator loanEventApiJsonValidator,
final LoanTransactionCommandFromApiJsonDeserializer loanTransactionCommandFromApiJsonDeserializer,
final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer, final LoanChargeCommandFromApiJsonDeserializer loanChargeCommandFromApiJsonDeserializer,
final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler, final LoanUpdateCommandFromApiJsonDeserializer loanUpdateCommandFromApiJsonDeserializer, final LoanAssembler loanAssembler,
final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository, final LoanRepository loanRepository, final LoanTransactionRepository loanTransactionRepository,
@@ -88,7 +80,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final JournalEntryWritePlatformService journalEntryWritePlatformService) { final JournalEntryWritePlatformService journalEntryWritePlatformService) {
this.context = context; this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanTransactionCommandFromApiJsonDeserializer = loanTransactionCommandFromApiJsonDeserializer;
this.loanChargeCommandFromApiJsonDeserializer = loanChargeCommandFromApiJsonDeserializer; this.loanChargeCommandFromApiJsonDeserializer = loanChargeCommandFromApiJsonDeserializer;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
@@ -101,10 +92,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer; this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer;
} }
private boolean isBeforeToday(final LocalDate date) {
return date.isBefore(DateUtils.getLocalDateOfTenant());
}
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values()); List<LoanStatus> allowedLoanStatuses = Arrays.asList(LoanStatus.values());
return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses);
@@ -193,11 +180,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Override @Override
public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command) { public CommandProcessingResult makeLoanRepayment(final Long loanId, final JsonCommand command) {
final AppUser currentUser = context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransaction(command.json());
.json());
loanTransactionCommand.validate();
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
@@ -205,12 +190,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
if (this.isBeforeToday(transactionDate) && currentUser.canNotMakeRepaymentOnLoanInPast()) { throw new NoAuthorizationException(
"error.msg.no.permission.to.make.repayment.on.loan.in.past"); }
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
@@ -226,9 +210,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds, final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds); existingReversedTransactionIds);
@@ -251,9 +232,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransaction(command.json());
.json());
loanTransactionCommand.validate();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
@@ -266,6 +245,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final List<Long> existingTransactionIds = new ArrayList<Long>(); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(); final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
@@ -286,9 +267,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds, final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds); existingReversedTransactionIds);
@@ -311,13 +289,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransaction(command.json());
.json());
loanTransactionCommand.validate();
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount")); changes.put("transactionAmount", command.stringValueOfParameterNamed("transactionAmount"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
@@ -333,9 +311,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(waiveTransaction.getId()) // .withEntityId(waiveTransaction.getId()) //
@@ -352,16 +327,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public CommandProcessingResult writeOff(final Long loanId, final JsonCommand command) { public CommandProcessingResult writeOff(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
.json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final LoanTransaction writeoff = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction writeoff = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds);
this.loanTransactionRepository.save(writeoff); this.loanTransactionRepository.save(writeoff);
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -373,14 +351,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
changes.put("dateFormat", command.dateFormat()); final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
// make a call to accounting journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
if (loan.isAccountingEnabledOnLoanProduct()) {
LoanDTO loanDTO = populateLoanData(loan, writeoff);
journalEntryWritePlatformService.createJournalEntriesForLoan(loanDTO);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
@@ -399,16 +373,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
.json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
final LoanTransaction possibleClosingTransaction = loan.close(command, defaultLoanLifecycleStateMachine(), changes); final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final LoanTransaction possibleClosingTransaction = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds);
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
this.loanTransactionRepository.save(possibleClosingTransaction); this.loanTransactionRepository.save(possibleClosingTransaction);
} }
@@ -421,8 +399,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale()); if (possibleClosingTransaction != null) {
changes.put("dateFormat", command.dateFormat()); final ApplicationCurrency currency = this.applicationCurrencyRepository.findOneByCode(loan.getCurrencyCode());
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(currency.toData(), existingTransactionIds,
existingReversedTransactionIds);
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
CommandProcessingResult result = null; CommandProcessingResult result = null;
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
@@ -456,14 +438,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonCommand command) { public CommandProcessingResult closeAsRescheduled(final Long loanId, final JsonCommand command) {
context.authenticatedUser(); context.authenticatedUser();
final LoanTransactionCommand loanTransactionCommand = this.loanTransactionCommandFromApiJsonDeserializer.commandFromApiJson(command this.loanEventApiJsonValidator.validateTransactionWithNoAmount(command.json());
.json());
loanTransactionCommand.validateNonMonetaryTransaction();
final Loan loan = retrieveLoanBy(loanId); final Loan loan = retrieveLoanBy(loanId);
final Map<String, Object> changes = new LinkedHashMap<String, Object>(); final Map<String, Object> changes = new LinkedHashMap<String, Object>();
changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate")); changes.put("transactionDate", command.stringValueOfParameterNamed("transactionDate"));
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes); loan.closeAsMarkedForReschedule(command, defaultLoanLifecycleStateMachine(), changes);
@@ -476,9 +458,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.noteRepository.save(note); this.noteRepository.save(note);
} }
changes.put("locale", command.locale());
changes.put("dateFormat", command.dateFormat());
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanId) // .withEntityId(loanId) //
@@ -719,24 +698,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.withLoanId(loanId) // .withLoanId(loanId) //
.build(); .build();
} }
private LoanDTO populateLoanDTO(final Loan loan, final List<LoanTransaction> loanTransactions) {
List<LoanTransactionDTO> loanTransactionDTOs = new ArrayList<LoanTransactionDTO>();
for (LoanTransaction loanTransaction : loanTransactions) {
LoanTransactionDTO loanTransactionDTO = new LoanTransactionDTO(loanTransaction.getId().toString(), loanTransaction.getDateOf(),
loanTransaction.getAmount(), loanTransaction.getPrincipalPortion(), loanTransaction.getInterestPortion(),
loanTransaction.getFeePortion(), loanTransaction.getPenaltyChargesPortion(), loanTransaction.isDisbursement(),
loanTransaction.isRepayment(), loanTransaction.isRepaymentAtDisbursement(), loanTransaction.isReversed(),
loanTransaction.isWriteOff());
loanTransactionDTOs.add(loanTransactionDTO);
}
return new LoanDTO(loan.getId(), loan.productId(), loan.getOfficeId(), loan.isCashBasedAccountingEnabledOnLoanProduct(),
loan.isAccrualBasedAccountingEnabledOnLoanProduct(), loanTransactionDTOs);
}
private LoanDTO populateLoanData(final Loan loan, final LoanTransaction loanTransaction) {
List<LoanTransaction> loanTransactions = new ArrayList<LoanTransaction>();
loanTransactions.add(loanTransaction);
return populateLoanDTO(loan, loanTransactions);
}
} }
@@ -625,11 +625,10 @@ public class SavingAccount extends AbstractAuditableCustom<AppUser, Long> {
public void postInterest() { public void postInterest() {
LocalDate lastInterestPostedDate = getLastInterestPostedDate(); LocalDate lastInterestPostedDate = getLastInterestPostedDate();
@SuppressWarnings("unused") // LocalDate nextInterestPostingDate = getNextInterestPostedDate();
LocalDate nextInterestPostingDate = getNextInterestPostedDate(); // SavingInterestCalculationMethod savingInterestCalculationMethod =
@SuppressWarnings("unused") // SavingInterestCalculationMethod
SavingInterestCalculationMethod savingInterestCalculationMethod = SavingInterestCalculationMethod // .fromInt(this.interestCalculationMethod);
.fromInt(this.interestCalculationMethod);
Integer monthsForInterestCalculation = Months.monthsBetween(lastInterestPostedDate, new LocalDate()).getMonths(); Integer monthsForInterestCalculation = Months.monthsBetween(lastInterestPostedDate, new LocalDate()).getMonths();
Integer postInterestItereations = monthsForInterestCalculation / this.interestPostEvery; Integer postInterestItereations = monthsForInterestCalculation / this.interestPostEvery;
@@ -644,7 +643,7 @@ public class SavingAccount extends AbstractAuditableCustom<AppUser, Long> {
while (postInterestItereations > 0) { while (postInterestItereations > 0) {
lastInterestPostedDate = getLastInterestPostedDate(); lastInterestPostedDate = getLastInterestPostedDate();
nextInterestPostingDate = getNextInterestPostedDate(); // nextInterestPostingDate = getNextInterestPostedDate();
LocalDate transactionStartDate = lastInterestPostedDate; LocalDate transactionStartDate = lastInterestPostedDate;
BigDecimal termTotalAmount = BigDecimal.ZERO; BigDecimal termTotalAmount = BigDecimal.ZERO;
@@ -687,7 +686,7 @@ public class SavingAccount extends AbstractAuditableCustom<AppUser, Long> {
isOutstandingAmountCalculated = true; isOutstandingAmountCalculated = true;
} }
// TODO- calculate interest on calculation method // TODO - HUGO - calculate interest on calculation method
// if (savingInterestCalculationMethod.isAverageBalance()) { // if (savingInterestCalculationMethod.isAverageBalance()) {
averageBalanceForTerm = BigDecimal.valueOf(termTotalAmount.doubleValue() / this.interestPostEvery); averageBalanceForTerm = BigDecimal.valueOf(termTotalAmount.doubleValue() / this.interestPostEvery);
@@ -45,12 +45,12 @@ public class CalculateSavingScheduleImpl implements CalculateSavingSchedule {
} }
@Override @Override
public SavingScheduleData calculateSavingSchedule(CalculateSavingScheduleCommand command) { public SavingScheduleData calculateSavingSchedule(final CalculateSavingScheduleCommand command) {
context.authenticatedUser(); context.authenticatedUser();
SavingScheduleData savingScheduleData = null; SavingScheduleData savingScheduleData = null;
// FIXME Validations should write here // FIXME - HUGO - Validations should write here
final SavingProduct savingProduct = this.savingProductRepository.findOne(command.getProductId()); final SavingProduct savingProduct = this.savingProductRepository.findOne(command.getProductId());
if (savingProduct == null) throw new SavingProductNotFoundException(command.getProductId()); if (savingProduct == null) throw new SavingProductNotFoundException(command.getProductId());
@@ -39,7 +39,7 @@ public class SavingProductEnumerations {
return savingsLockinPeriod(SavingsLockinPeriodEnum.fromInt(id)); return savingsLockinPeriod(SavingsLockinPeriodEnum.fromInt(id));
} }
public static EnumOptionData savingsLockinPeriod(SavingsLockinPeriodEnum type) { public static EnumOptionData savingsLockinPeriod(final SavingsLockinPeriodEnum type) {
final String codePrefix = "savings.product."; final String codePrefix = "savings.product.";
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type) { switch (type) {
@@ -67,7 +67,7 @@ public class SavingProductEnumerations {
return optionData; return optionData;
} }
public static EnumOptionData savingInterestCalculationMethod(SavingInterestCalculationMethod type) { public static EnumOptionData savingInterestCalculationMethod(final SavingInterestCalculationMethod type) {
final String codePrefix = "savings.interestCalculation.method."; final String codePrefix = "savings.interestCalculation.method.";
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type) { switch (type) {
@@ -95,7 +95,7 @@ public class SavingProductEnumerations {
return optionData; return optionData;
} }
public static EnumOptionData savingInterestType(SavingsInterestType type) { public static EnumOptionData savingInterestType(final SavingsInterestType type) {
final String codePrefix = "savings.interest.type."; final String codePrefix = "savings.interest.type.";
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type) { switch (type) {
@@ -115,7 +115,7 @@ public class SavingProductEnumerations {
return optionData; return optionData;
} }
public static EnumOptionData interestFrequencyType(SavingFrequencyType type) { public static EnumOptionData interestFrequencyType(final SavingFrequencyType type) {
final String codePrefix = "saving.product.frequency.period."; final String codePrefix = "saving.product.frequency.period.";
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type) { switch (type) {
@@ -171,7 +171,6 @@ public class SavingProductEnumerations {
final String codePrefix = "savings.product."; final String codePrefix = "savings.product.";
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type) { switch (type) {
// FIXME - Madhukar - spelling mistake on 'Recurring'
case RECURRING: case RECURRING:
optionData = new EnumOptionData(SavingProductType.RECURRING.getValue().longValue(), codePrefix optionData = new EnumOptionData(SavingProductType.RECURRING.getValue().longValue(), codePrefix
+ SavingProductType.RECURRING.getCode(), "Recurring"); + SavingProductType.RECURRING.getCode(), "Recurring");
@@ -16,11 +16,11 @@ import org.springframework.stereotype.Service;
@Service @Service
public class FixedTermDepositInterestCalculator { public class FixedTermDepositInterestCalculator {
// FIXME - MADHUKAR - Are the unused field here needed? // FIXME - HUGO - Are the unused field here needed?
public Money calculateInterestOnMaturityFor(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate, public Money calculateInterestOnMaturityFor(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate,
final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) { final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) {
// TODO - Can change 'interest compounding' fields into something // TODO - HUGO - Can change 'interest compounding' fields into something
// simpler like interestCompoundedFrequency {None (Simple Interest), // simpler like interestCompoundedFrequency {None (Simple Interest),
// Monthly, Quarterly, Half-Yearly, Annually} // Monthly, Quarterly, Half-Yearly, Annually}
// - the 'interestCompoundedEvery' would then be inferred form the // - the 'interestCompoundedEvery' would then be inferred form the
@@ -28,7 +28,7 @@ public class FixedTermDepositInterestCalculator {
// - the special case of 'simple interest' or 'no compounding' would use // - the special case of 'simple interest' or 'no compounding' would use
// simple formula rather than 'future value' function. // simple formula rather than 'future value' function.
// FIXME - assuming only monthly compounding period for now // FIXME - HUGO - assuming only monthly compounding period for now
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer monthsInYear = 12; Integer monthsInYear = 12;
@@ -59,8 +59,8 @@ public class FixedTermDepositInterestCalculator {
* payments are due at the beginning of period (false => payments are due at * payments are due at the beginning of period (false => payments are due at
* end of period) * end of period)
*/ */
private static double fv(double ratePeriodCompoundingPeriod, double numberOfCompoundingPeriods, double pmtPerPeriod, private static double fv(final double ratePeriodCompoundingPeriod, final double numberOfCompoundingPeriods, final double pmtPerPeriod,
double presentValue, boolean type) { final double presentValue, final boolean type) {
double retval = 0; double retval = 0;
if (ratePeriodCompoundingPeriod == 0) { if (ratePeriodCompoundingPeriod == 0) {
retval = -1 * (presentValue + (numberOfCompoundingPeriods * pmtPerPeriod)); retval = -1 * (presentValue + (numberOfCompoundingPeriods * pmtPerPeriod));
@@ -72,8 +72,8 @@ public class FixedTermDepositInterestCalculator {
return retval; return retval;
} }
public Money calculateInterestOnMaturityForSimpleInterest(Money deposit, Integer tenureInMonths, BigDecimal maturityInterestRate, public Money calculateInterestOnMaturityForSimpleInterest(final Money deposit, final Integer tenureInMonths, final BigDecimal maturityInterestRate,
Integer interestCompoundedEvery, PeriodFrequencyType interestCompoundedFrequencyPeriodType) { final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer monthsInYear = 12; Integer monthsInYear = 12;
@@ -91,7 +91,7 @@ public class FixedTermDepositInterestCalculator {
return Money.of(deposit.getCurrency(), totalAmount); return Money.of(deposit.getCurrency(), totalAmount);
} }
public Money calculateRemainInterest(Money deposit, Integer days, BigDecimal interestRate) { public Money calculateRemainInterest(final Money deposit, final Integer days, final BigDecimal interestRate) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer daysInYear = 365; Integer daysInYear = 365;