initial work on transfers
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||||
|
VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP', 'GROUP', 'TRANSFERCLIENTS', 0);
|
||||||
|
|
||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||||
|
VALUES ('portfolio_group', 'TRANSFERCLIENTS_GROUP_CHECKER', 'GROUP', 'TRANSFERCLIENTS', 0);
|
||||||
|
|
||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||||
|
VALUES ('portfolio', 'TRANSFER_CLIENT', 'CLIENT', 'TRANSFER', 0);
|
||||||
|
|
||||||
|
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`)
|
||||||
|
VALUES ('portfolio', 'TRANSFER_CLIENT_CHECKER', 'CLIENT', 'TRANSFER', 0);
|
||||||
@@ -552,6 +552,10 @@ public class CommandWrapper {
|
|||||||
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("UPDATEROLE");
|
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("UPDATEROLE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTransferClientsBetweenGroups() {
|
||||||
|
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("TRANSFERCLIENTS");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isClientAssignStaff() {
|
public boolean isClientAssignStaff() {
|
||||||
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF") && this.entityName.equalsIgnoreCase("CLIENT");
|
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||||
}
|
}
|
||||||
@@ -560,6 +564,10 @@ public class CommandWrapper {
|
|||||||
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
|
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isClientTransfer() {
|
||||||
|
return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isProductMixResource() {
|
public boolean isProductMixResource() {
|
||||||
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
|
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -352,6 +352,15 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder transferClient(final Long clientId) {
|
||||||
|
this.actionName = "TRANSFER";
|
||||||
|
this.entityName = "CLIENT";
|
||||||
|
this.entityId = clientId;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.href = "/clientId/" + clientId + "?command=transfer";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder updateClient(final Long clientId) {
|
public CommandWrapperBuilder updateClient(final Long clientId) {
|
||||||
this.actionName = "UPDATE";
|
this.actionName = "UPDATE";
|
||||||
this.entityName = "CLIENT";
|
this.entityName = "CLIENT";
|
||||||
@@ -1001,6 +1010,15 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder transferClientsBetweenGroups(final Long sourceGroupId) {
|
||||||
|
this.actionName = "TRANSFERCLIENTS";
|
||||||
|
this.entityName = "GROUP";
|
||||||
|
this.entityId = sourceGroupId;
|
||||||
|
this.groupId = sourceGroupId;
|
||||||
|
this.href = "/groups/" + sourceGroupId + "?command=transferClients";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder unassignGroupStaff(final Long groupId) {
|
public CommandWrapperBuilder unassignGroupStaff(final Long groupId) {
|
||||||
this.actionName = "UNASSIGNSTAFF";
|
this.actionName = "UNASSIGNSTAFF";
|
||||||
this.entityName = "GROUP";
|
this.entityName = "GROUP";
|
||||||
|
|||||||
+5
-1
@@ -170,6 +170,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
|
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
|
||||||
} else if (wrapper.isClientClose()) {
|
} else if (wrapper.isClientClose()) {
|
||||||
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
|
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
} else if (wrapper.isClientTransfer()) {
|
||||||
|
handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class);
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedCommandException(wrapper.commandName());
|
throw new UnsupportedCommandException(wrapper.commandName());
|
||||||
}
|
}
|
||||||
@@ -452,7 +454,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
|||||||
handler = this.applicationContext.getBean("updateGroupRoleCommandHandler", NewCommandSourceHandler.class);
|
handler = this.applicationContext.getBean("updateGroupRoleCommandHandler", NewCommandSourceHandler.class);
|
||||||
} else if (wrapper.isAssignStaff()) {
|
} else if (wrapper.isAssignStaff()) {
|
||||||
handler = this.applicationContext.getBean("assignGroupStaffCommandHandler", NewCommandSourceHandler.class);
|
handler = this.applicationContext.getBean("assignGroupStaffCommandHandler", NewCommandSourceHandler.class);
|
||||||
}else {
|
} else if (wrapper.isTransferClientsBetweenGroups()) {
|
||||||
|
handler = this.applicationContext.getBean("transferClientsBetweenGroupsCommandHandler", NewCommandSourceHandler.class);
|
||||||
|
} else {
|
||||||
throw new UnsupportedCommandException(wrapper.commandName());
|
throw new UnsupportedCommandException(wrapper.commandName());
|
||||||
}
|
}
|
||||||
} else if (wrapper.isCenterResource()) {
|
} else if (wrapper.isCenterResource()) {
|
||||||
|
|||||||
+1
@@ -128,6 +128,7 @@ public class DataValidatorBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***FIXME: Vishwas, why does this method have a parameter? Seems wrong***/
|
||||||
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
|
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
|
||||||
if (!trueOfFalseFieldProvided && !ignoreNullValue) {
|
if (!trueOfFalseFieldProvided && !ignoreNullValue) {
|
||||||
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(resource).append(".").append(parameter)
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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.organisation.office.domain;
|
||||||
|
|
||||||
|
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Wrapper for {@link OfficeRepository} that adds NULL checking and Error
|
||||||
|
* handling capabilities
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class OfficeRepositoryWrapper {
|
||||||
|
|
||||||
|
private final OfficeRepository repository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public OfficeRepositoryWrapper(final OfficeRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Office findOneWithNotFoundDetection(final Long id) {
|
||||||
|
final Office office = this.repository.findOne(id);
|
||||||
|
if (office == null) { throw new OfficeNotFoundException(id); }
|
||||||
|
return office;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(final Office entity) {
|
||||||
|
this.repository.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAndFlush(final Office entity) {
|
||||||
|
this.repository.saveAndFlush(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(final Office entity) {
|
||||||
|
this.repository.delete(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -6,6 +6,7 @@
|
|||||||
package org.mifosplatform.portfolio.calendar.domain;
|
package org.mifosplatform.portfolio.calendar.domain;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
@@ -18,8 +19,15 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
|
|||||||
|
|
||||||
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
|
Collection<CalendarInstance> findByEntityIdAndEntityTypeId(Long entityId, Integer entityTypeId);
|
||||||
|
|
||||||
|
CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId);
|
||||||
|
|
||||||
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
||||||
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
||||||
|
|
||||||
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
|
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
|
||||||
|
|
||||||
|
/**Should use in clause, can I do it without creating a new class?**/
|
||||||
|
@Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id=:groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3")
|
||||||
|
List<CalendarInstance> findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("clientId") Long clientId,
|
||||||
|
@Param("groupId") Long groupId);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -207,6 +207,9 @@ public class ClientsApiResource {
|
|||||||
} else if (is(commandParam, "close")) {
|
} else if (is(commandParam, "close")) {
|
||||||
commandRequest = builder.closeClient(clientId).build();
|
commandRequest = builder.closeClient(clientId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
}else if (is(commandParam, "transfer")) {
|
||||||
|
commandRequest = builder.transferClient(clientId).build();
|
||||||
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate",
|
if (result == null) { throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate",
|
||||||
|
|||||||
@@ -430,6 +430,10 @@ public final class Client extends AbstractPersistable<Long> {
|
|||||||
return this.office;
|
return this.office;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateOffice(final Office office) {
|
||||||
|
this.office = office;
|
||||||
|
}
|
||||||
|
|
||||||
private Long staffId() {
|
private Long staffId() {
|
||||||
Long staffId = null;
|
Long staffId = null;
|
||||||
if (this.staff != null) {
|
if (this.staff != null) {
|
||||||
@@ -454,6 +458,10 @@ public final class Client extends AbstractPersistable<Long> {
|
|||||||
this.staff = staff;
|
this.staff = staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<Group> getGroups(){
|
||||||
|
return this.groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void close(final Long closureReasonId, final Date closureDate) {
|
public void close(final Long closureReasonId, final Date closureDate) {
|
||||||
this.closureReasonId = closureReasonId;
|
this.closureReasonId = closureReasonId;
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package org.mifosplatform.portfolio.client.exception;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
public class ClientHasBeenClosedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public ClientHasBeenClosedException(final Long clientId) {
|
||||||
|
super("error.msg.client.closed", "Client with identifier " + clientId + " has already been closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
@@ -306,6 +306,11 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP
|
|||||||
final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
|
final Long staffId = command.longValueOfParameterNamed(ClientApiConstants.staffIdParamName);
|
||||||
if (staffId != null) {
|
if (staffId != null) {
|
||||||
staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientForUpdate.getOffice().getHierarchy());
|
staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientForUpdate.getOffice().getHierarchy());
|
||||||
|
/**
|
||||||
|
* TODO Vishwas: We maintain history of chage of loan officer w.r.t
|
||||||
|
* loan in a history table, should we do the same for a client?
|
||||||
|
* Especially useful when the change happens due to a transfer etc
|
||||||
|
**/
|
||||||
clientForUpdate.assignStaff(staff);
|
clientForUpdate.assignStaff(staff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -329,6 +329,10 @@ public class GroupsApiResource {
|
|||||||
final CommandWrapper commandRequest = builder.updateRole(groupId, roleId).build();
|
final CommandWrapper commandRequest = builder.updateRole(groupId, roleId).build();
|
||||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
return this.toApiJsonSerializer.serialize(result);
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
|
} else if (is(commandParam, "transferClients")) {
|
||||||
|
final CommandWrapper commandRequest = builder.transferClientsBetweenGroups(groupId).build();
|
||||||
|
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
return this.toApiJsonSerializer.serialize(result);
|
||||||
} else {
|
} else {
|
||||||
throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet",
|
throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet",
|
||||||
"saveCollectionSheet", "unassignStaff", "assignRole", "unassignRole", "updateassignRole" });
|
"saveCollectionSheet", "unassignStaff", "assignRole", "unassignRole", "updateassignRole" });
|
||||||
|
|||||||
+16
-14
@@ -1114,7 +1114,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
|
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
//validate if disbursement date is a holiday or a non-working day
|
// validate if disbursement date is a holiday or a non-working day
|
||||||
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
||||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||||
}
|
}
|
||||||
@@ -1332,14 +1332,15 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
actualChanges.put("dateFormat", command.dateFormat());
|
actualChanges.put("dateFormat", command.dateFormat());
|
||||||
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
|
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
|
||||||
|
|
||||||
//validate if disbursement date is a holiday or a non-working day
|
// validate if disbursement date is a holiday or a non-working day
|
||||||
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
||||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||||
|
|
||||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId);
|
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId);
|
||||||
|
|
||||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
|
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||||
|
holidays, workingDays);
|
||||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||||
} else {
|
} else {
|
||||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||||
@@ -1366,8 +1367,8 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
* details/state.
|
* details/state.
|
||||||
*/
|
*/
|
||||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||||
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
|
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
|
||||||
final List<Holiday> holidays, final WorkingDays workingDays) {
|
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||||
|
|
||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||||
@@ -1386,7 +1387,8 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
updateLoanSchedule(loanSchedule);
|
updateLoanSchedule(loanSchedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn, final String txnExternalId) {
|
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn,
|
||||||
|
final String txnExternalId) {
|
||||||
// track disbursement transaction
|
// track disbursement transaction
|
||||||
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(),
|
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(),
|
||||||
paymentDetail, disbursedOn, txnExternalId);
|
paymentDetail, disbursedOn, txnExternalId);
|
||||||
@@ -2387,7 +2389,6 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
||||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||||
|
|
||||||
//first repayment's from date is same as disbursement date.
|
//first repayment's from date is same as disbursement date.
|
||||||
LocalDate tmpFromDate = this.getDisbursementDate();
|
LocalDate tmpFromDate = this.getDisbursementDate();
|
||||||
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
||||||
@@ -2483,9 +2484,10 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
|
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) {
|
||||||
if (!allowTransactionsOnNonWorkingDay) {
|
if (!allowTransactionsOnNonWorkingDay) {
|
||||||
if(!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())){
|
if (!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())) {
|
||||||
final String errorMessage = "Expected disbursement date cannot be on a non working day";
|
final String errorMessage = "Expected disbursement date cannot be on a non working day";
|
||||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, getExpectedDisbursedOnLocalDate());
|
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage,
|
||||||
|
getExpectedDisbursedOnLocalDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2494,8 +2496,7 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
if (!allowTransactionsOnHoliday) {
|
if (!allowTransactionsOnHoliday) {
|
||||||
if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
|
if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
|
||||||
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
||||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage,
|
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||||
getExpectedDisbursedOnLocalDate());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2524,6 +2525,10 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
return this.group;
|
return this.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateGroup(Group newGroup) {
|
||||||
|
this.group = newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getCurrentLoanCounter() {
|
public Integer getCurrentLoanCounter() {
|
||||||
return this.loanCounter;
|
return this.loanCounter;
|
||||||
}
|
}
|
||||||
@@ -2564,8 +2569,5 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateGroup(Group group) {
|
|
||||||
this.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+10
-2
@@ -52,6 +52,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
|||||||
public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where "
|
public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where "
|
||||||
+ "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id";
|
+ "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id";
|
||||||
|
|
||||||
|
public static final String DOES_CLIENT_HAVE_ACTIVE_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300)";
|
||||||
|
|
||||||
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
||||||
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
|
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
|
||||||
@Param("loanType") Integer loanType);
|
@Param("loanType") Integer loanType);
|
||||||
@@ -101,15 +103,21 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
|||||||
List<Long> getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
|
List<Long> getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
|
||||||
|
|
||||||
@Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
@Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
||||||
List<Loan> findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds, @Param("loanStatuses") Collection<Integer> loanStatuses);
|
List<Loan> findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds,
|
||||||
|
@Param("loanStatuses") Collection<Integer> loanStatuses);
|
||||||
|
|
||||||
@Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
@Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
|
||||||
List<Loan> findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds, @Param("loanStatuses") Collection<Integer> loanStatuses);
|
List<Loan> findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection<Long> officeIds,
|
||||||
|
@Param("loanStatuses") Collection<Integer> loanStatuses);
|
||||||
|
|
||||||
|
/*** FIXME: Add more appropriate names for the query ***/
|
||||||
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT)
|
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT)
|
||||||
List<Long> findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus);
|
List<Long> findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus);
|
||||||
|
|
||||||
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
|
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
|
||||||
List<Long> findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus);
|
List<Long> findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus);
|
||||||
|
|
||||||
|
@Query(DOES_CLIENT_HAVE_ACTIVE_LOANS)
|
||||||
|
boolean doesClientHaveActiveLoans(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
}
|
}
|
||||||
+4
@@ -111,4 +111,8 @@ public enum LoanStatus {
|
|||||||
public boolean isRejected() {
|
public boolean isRejected() {
|
||||||
return this.value.equals(LoanStatus.REJECTED.getValue());
|
return this.value.equals(LoanStatus.REJECTED.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isActiveOrAwaitingApprovalOrDisbursal() {
|
||||||
|
return this.isApproved() || this.isSubmittedAndPendingApproval() || this.isActive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.api;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TransferApiConstants {
|
||||||
|
|
||||||
|
// general
|
||||||
|
public static final String localeParamName = "locale";
|
||||||
|
public static final String dateFormatParamName = "dateFormat";
|
||||||
|
|
||||||
|
// request parameters
|
||||||
|
public static final String idParamName = "id";
|
||||||
|
public static final String destinationGroupIdParamName = "destinationGroupId";
|
||||||
|
public static final String clients = "clients";
|
||||||
|
public static final String inheritDestinationGroupLoanOfficer = "inheritDestinationGroupLoanOfficer";
|
||||||
|
public static final String newStaffIdParamName = "newStaffId";
|
||||||
|
public static final String transferActiveLoans = "transferActiveLoans";
|
||||||
|
public static final String destinationOfficeIdParamName = "destinationOfficeIdParamName";
|
||||||
|
|
||||||
|
public static final Set<String> TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
|
||||||
|
dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName,
|
||||||
|
transferActiveLoans));
|
||||||
|
|
||||||
|
public static final Set<String> TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
|
||||||
|
dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans));
|
||||||
|
|
||||||
|
}
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.data;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
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.client.api.ClientApiConstants;
|
||||||
|
import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants;
|
||||||
|
import org.mifosplatform.portfolio.transfer.api.TransferApiConstants;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public final class TransfersDataValidator {
|
||||||
|
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TransfersDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||||
|
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
|
||||||
|
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForClientsTransferBetweenGroups(final String json) {
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||||
|
TransferApiConstants.TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS);
|
||||||
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||||
|
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||||
|
.resource(GroupingTypesApiConstants.GROUP_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull()
|
||||||
|
.integerGreaterThanZero();
|
||||||
|
|
||||||
|
if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) {
|
||||||
|
final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull()
|
||||||
|
.integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.transferActiveLoans, element)) {
|
||||||
|
final Boolean inheritDestinationGroupLoanOfficer = this.fromApiJsonHelper.extractBooleanNamed(
|
||||||
|
TransferApiConstants.transferActiveLoans, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.transferActiveLoans).value(inheritDestinationGroupLoanOfficer)
|
||||||
|
.notNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.inheritDestinationGroupLoanOfficer, element)) {
|
||||||
|
final Boolean inheritDestinationGroupLoanOfficer = this.fromApiJsonHelper.extractBooleanNamed(
|
||||||
|
TransferApiConstants.inheritDestinationGroupLoanOfficer, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.inheritDestinationGroupLoanOfficer)
|
||||||
|
.value(inheritDestinationGroupLoanOfficer).notNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForClientTransferBetweenBranches(final String json) {
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||||
|
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
|
||||||
|
TransferApiConstants.TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS);
|
||||||
|
final JsonElement element = this.fromApiJsonHelper.parse(json);
|
||||||
|
|
||||||
|
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||||
|
|
||||||
|
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
|
||||||
|
.resource(ClientApiConstants.CLIENT_RESOURCE_NAME);
|
||||||
|
|
||||||
|
final Long destinationOfficeId = this.fromApiJsonHelper
|
||||||
|
.extractLongNamed(TransferApiConstants.destinationOfficeIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.destinationOfficeIdParamName).value(destinationOfficeId).notNull()
|
||||||
|
.integerGreaterThanZero();
|
||||||
|
|
||||||
|
if (this.fromApiJsonHelper.parameterExists(TransferApiConstants.newStaffIdParamName, element)) {
|
||||||
|
final Long newStaffId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.newStaffIdParamName, element);
|
||||||
|
baseDataValidator.reset().parameter(TransferApiConstants.newStaffIdParamName).value(newStaffId).notNull()
|
||||||
|
.integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.exception;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AbstractPlatformDomainRuleException} thrown when not supported loan
|
||||||
|
* template type is sent.
|
||||||
|
*/
|
||||||
|
public class TransferNotSupportedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Constructor (cannot transfer entities with active loan/savings
|
||||||
|
* accounts)
|
||||||
|
*/
|
||||||
|
public TransferNotSupportedException() {
|
||||||
|
super("error.msg.entity.transfers.with.active.accounts", "Cannot transfer Clients/Groups having active loan or savings accounts");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded Constructor (Cannot transfer Clients with active accounts
|
||||||
|
* between groups with different meeting frequency)
|
||||||
|
*
|
||||||
|
* @param clientId
|
||||||
|
* @param sourceGroupId
|
||||||
|
* @param destinationGroupId
|
||||||
|
*/
|
||||||
|
public TransferNotSupportedException(Long clientId, Long sourceGroupId, Long destinationGroupId) {
|
||||||
|
super("error.msg.client.transfers.with.active.accounts.between.groups.with.different.meeting.frequency",
|
||||||
|
"Cannot transfer Clients with active accounts between groups with different meeting frequency", clientId, sourceGroupId,
|
||||||
|
destinationGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded Constructor (Cannot transfer Clients with active (JLG)
|
||||||
|
* accounts to a group with no meetings defined)
|
||||||
|
*
|
||||||
|
* @param clientId
|
||||||
|
* @param sourceGroupId
|
||||||
|
* @param destinationGroupId
|
||||||
|
*/
|
||||||
|
public TransferNotSupportedException(Long clientId, Long sourceGroupId, Long destinationGroupId, boolean destinationGroupMeetingAbsent) {
|
||||||
|
super("error.msg.client.transfers.with.active.accounts.to.group.with.no.meeting.frequency",
|
||||||
|
"Cannot transfer Client with active accounts to a groups with no meeting frequency", clientId, sourceGroupId,
|
||||||
|
destinationGroupId,destinationGroupMeetingAbsent);
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.handler;
|
||||||
|
|
||||||
|
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TransferClientBetweenBranchesCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final TransferWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TransferClientBetweenBranchesCommandHandler(final TransferWritePlatformService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
return this.writePlatformService.transferClientBetweenBranches(command.entityId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.handler;
|
||||||
|
|
||||||
|
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.portfolio.transfer.service.TransferWritePlatformService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TransferClientsBetweenGroupsCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final TransferWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TransferClientsBetweenGroupsCommandHandler(final TransferWritePlatformService writePlatformService) {
|
||||||
|
this.writePlatformService = writePlatformService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
|
||||||
|
return this.writePlatformService.transferClientsBetweenGroups(command.entityId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.service;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
|
||||||
|
public interface TransferWritePlatformService {
|
||||||
|
|
||||||
|
CommandProcessingResult transferClientsBetweenGroups(final Long sourceGroupId, final JsonCommand jsonCommand);
|
||||||
|
|
||||||
|
CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand);
|
||||||
|
|
||||||
|
}
|
||||||
+260
@@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* 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.transfer.service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||||
|
import org.mifosplatform.organisation.office.domain.Office;
|
||||||
|
import org.mifosplatform.organisation.office.domain.OfficeRepositoryWrapper;
|
||||||
|
import org.mifosplatform.organisation.staff.domain.Staff;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
|
||||||
|
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
|
||||||
|
import org.mifosplatform.portfolio.client.domain.Client;
|
||||||
|
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.mifosplatform.portfolio.client.exception.ClientHasBeenClosedException;
|
||||||
|
import org.mifosplatform.portfolio.group.domain.Group;
|
||||||
|
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
|
||||||
|
import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
|
import org.mifosplatform.portfolio.transfer.api.TransferApiConstants;
|
||||||
|
import org.mifosplatform.portfolio.transfer.data.TransfersDataValidator;
|
||||||
|
import org.mifosplatform.portfolio.transfer.exception.TransferNotSupportedException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWritePlatformService {
|
||||||
|
|
||||||
|
private final ClientRepositoryWrapper clientRepository;
|
||||||
|
private final OfficeRepositoryWrapper officeRepository;
|
||||||
|
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||||
|
private final GroupRepositoryWrapper groupRepository;
|
||||||
|
private final LoanWritePlatformService loanWritePlatformService;
|
||||||
|
private final LoanRepository loanRepository;
|
||||||
|
private final TransfersDataValidator transfersDataValidator;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepository,
|
||||||
|
final OfficeRepositoryWrapper officeRepository, final CalendarInstanceRepository calendarInstanceRepository,
|
||||||
|
final LoanWritePlatformService loanWritePlatformService, final GroupRepositoryWrapper groupRepository,
|
||||||
|
final LoanRepository loanRepository, final TransfersDataValidator transfersDataValidator) {
|
||||||
|
this.clientRepository = clientRepository;
|
||||||
|
this.officeRepository = officeRepository;
|
||||||
|
this.calendarInstanceRepository = calendarInstanceRepository;
|
||||||
|
this.loanWritePlatformService = loanWritePlatformService;
|
||||||
|
this.groupRepository = groupRepository;
|
||||||
|
this.loanRepository = loanRepository;
|
||||||
|
this.transfersDataValidator = transfersDataValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CommandProcessingResult transferClientsBetweenGroups(Long sourceGroupId, JsonCommand jsonCommand) {
|
||||||
|
this.transfersDataValidator.validateForClientsTransferBetweenGroups(jsonCommand.json());
|
||||||
|
|
||||||
|
final Group sourceGroup = this.groupRepository.findOneWithNotFoundDetection(sourceGroupId);
|
||||||
|
final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName);
|
||||||
|
final Group destinationGroup = this.groupRepository.findOneWithNotFoundDetection(destinationGroupId);
|
||||||
|
|
||||||
|
final List<Client> clients = this.assembleListOfClients(jsonCommand);
|
||||||
|
|
||||||
|
for (Client client : clients) {
|
||||||
|
transferClientBetweenGroups(sourceGroup, client, destinationGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withEntityId(sourceGroupId) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****
|
||||||
|
* Variables that would make sense <br/>
|
||||||
|
* <ul>
|
||||||
|
* <li>inheritDestinationGroupLoanOfficer: Default False</li>
|
||||||
|
* <li>newStaffId: Optional field with Id of new Loan Officer to be linked
|
||||||
|
* to this client and all his JLG loans for this group</li>
|
||||||
|
* <li>transferActiveLoans:Default False, unless overridden, this client
|
||||||
|
* cannot be transferred between groups within the same branch if he has
|
||||||
|
* active JLG loans and across branches if he has any active loan</li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* <li></li>
|
||||||
|
* </ul>
|
||||||
|
* ***/
|
||||||
|
@Transactional
|
||||||
|
public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup) {
|
||||||
|
|
||||||
|
// next I shall validate that the client is present in this group
|
||||||
|
if (!sourceGroup.hasClientAsMember(client)) { throw new ClientNotInGroupException(client.getId(), sourceGroup.getId()); }
|
||||||
|
// Is client active?
|
||||||
|
if (client.isNotActive()) { throw new ClientHasBeenClosedException(client.getId()); }
|
||||||
|
|
||||||
|
/*** Code for intra-branch transfers ***/
|
||||||
|
if (client.getOffice().getId() == destinationGroup.getOffice().getId()) {
|
||||||
|
/**
|
||||||
|
* TODO: for now we need to ensure that only one collection sheet
|
||||||
|
* calendar can be linked with a center or group entity <br/>
|
||||||
|
**/
|
||||||
|
final CalendarInstance sourceGroupCalendarInstance = this.calendarInstanceRepository
|
||||||
|
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(sourceGroup.getId(), CalendarEntityType.GROUPS.getValue(),
|
||||||
|
CalendarType.COLLECTION.getValue());
|
||||||
|
// get all customer loans synced with this group calendar Instance
|
||||||
|
final List<CalendarInstance> activeLoanCalendarInstances = this.calendarInstanceRepository
|
||||||
|
.findCalendarInstancesForActiveLoansByGroupIdAndClientId(sourceGroup.getId(), client.getId());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if a calendar is present in the source group along with loans
|
||||||
|
* synced with it, we should ensure that the destination group also
|
||||||
|
* has a collection calendar
|
||||||
|
**/
|
||||||
|
if (sourceGroupCalendarInstance != null && !activeLoanCalendarInstances.isEmpty()) {
|
||||||
|
// get the destination calendar
|
||||||
|
final CalendarInstance destinationGroupCalendarInstance = this.calendarInstanceRepository
|
||||||
|
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(destinationGroup.getId(), CalendarEntityType.GROUPS.getValue(),
|
||||||
|
CalendarType.COLLECTION.getValue());
|
||||||
|
|
||||||
|
if (destinationGroupCalendarInstance == null) { throw new TransferNotSupportedException(client.getId(),
|
||||||
|
sourceGroup.getId(), destinationGroup.getId(), false);
|
||||||
|
|
||||||
|
}
|
||||||
|
Calendar sourceGroupCalendar = destinationGroupCalendarInstance.getCalendar();
|
||||||
|
Calendar destinationGroupCalendar = destinationGroupCalendarInstance.getCalendar();
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Ensure that the recurrence pattern are same for collection
|
||||||
|
* meeting in both the source and the destination calendar
|
||||||
|
***/
|
||||||
|
if (!(CalendarUtils.isFrequencySame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()) && CalendarUtils
|
||||||
|
.isIntervalSame(sourceGroupCalendar.getRecurrence(), destinationGroupCalendar.getRecurrence()))) { throw new TransferNotSupportedException(
|
||||||
|
client.getId(), sourceGroup.getId(), destinationGroup.getId()); }
|
||||||
|
|
||||||
|
/** map all JLG loans for this client to the destinationGroup **/
|
||||||
|
for (CalendarInstance calendarInstance : activeLoanCalendarInstances) {
|
||||||
|
calendarInstance.updateCalendar(destinationGroupCalendar);
|
||||||
|
}
|
||||||
|
// reschedule all JLG Loans to follow new Calendar
|
||||||
|
this.loanWritePlatformService.applyMeetingDateChanges(destinationGroupCalendar, activeLoanCalendarInstances);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Office destinationOffice = destinationGroup.getOffice();
|
||||||
|
transferClientToNewBranch(client, destinationOffice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now Change the loan officer for this client and all his active JLG
|
||||||
|
* loans
|
||||||
|
**/
|
||||||
|
Staff destinationGroupLoanOfficer = destinationGroup.getStaff();
|
||||||
|
if (destinationGroupLoanOfficer != null) {
|
||||||
|
client.updateStaff(destinationGroupLoanOfficer);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.getGroups().add(destinationGroup);
|
||||||
|
this.clientRepository.saveAndFlush(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active JLG loans are now linked to the new Group and Loan officer
|
||||||
|
**/
|
||||||
|
List<Loan> allClientJLGLoans = loanRepository.findByClientIdAndGroupId(client.getId(), sourceGroup.getId());
|
||||||
|
for (Loan loan : allClientJLGLoans) {
|
||||||
|
if (loan.status().isActiveOrAwaitingApprovalOrDisbursal()) {
|
||||||
|
loan.updateGroup(destinationGroup);
|
||||||
|
if (destinationGroupLoanOfficer != null) {
|
||||||
|
loan.reassignLoanOfficer(destinationGroupLoanOfficer, DateUtils.getLocalDateOfTenant());
|
||||||
|
}
|
||||||
|
this.loanRepository.saveAndFlush(loan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// change client group membership
|
||||||
|
client.getGroups().remove(sourceGroup);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This API is meant for transferring clients between branches mainly by
|
||||||
|
* Organizations following an Individual lending Model <br/>
|
||||||
|
* If the Client is linked to any Groups, we can optionally choose to have
|
||||||
|
* all the linkages broken and all JLG Loans are converted into Individual
|
||||||
|
* Loans
|
||||||
|
*
|
||||||
|
* @param clientId
|
||||||
|
* @param destinationOfficeId
|
||||||
|
* @param jsonCommand
|
||||||
|
* @return
|
||||||
|
**/
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult transferClientBetweenBranches(final Long clientId, final JsonCommand jsonCommand) {
|
||||||
|
// validation
|
||||||
|
this.transfersDataValidator.validateForClientsTransferBetweenGroups(jsonCommand.json());
|
||||||
|
|
||||||
|
final Long destinationOfficeId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationOfficeIdParamName);
|
||||||
|
final Office office = this.officeRepository.findOneWithNotFoundDetection(destinationOfficeId);
|
||||||
|
final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId);
|
||||||
|
/*** Remove all group associations for this client ***/
|
||||||
|
client.getGroups().clear();
|
||||||
|
transferClientToNewBranch(client, office);
|
||||||
|
this.clientRepository.save(client);
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder() //
|
||||||
|
.withClientId(clientId) //
|
||||||
|
.withEntityId(clientId) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transferClientToNewBranch(final Client client, final Office destinationOffice) {
|
||||||
|
/**
|
||||||
|
* For now, do not allow Clients with active loans to be transferred
|
||||||
|
* between branches
|
||||||
|
**/
|
||||||
|
if (loanRepository.doesClientHaveActiveLoans(client.getId())) { throw new TransferNotSupportedException(); }
|
||||||
|
// set the new office for the client
|
||||||
|
client.updateOffice(destinationOffice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Client> assembleListOfClients(final JsonCommand command) {
|
||||||
|
|
||||||
|
final List<Client> clients = new ArrayList<Client>();
|
||||||
|
|
||||||
|
if (command.parameterExists(TransferApiConstants.clients)) {
|
||||||
|
JsonArray clientsArray = command.arrayOfParameterNamed(TransferApiConstants.clients);
|
||||||
|
if (clientsArray != null) {
|
||||||
|
for (int i = 0; i < clientsArray.size(); i++) {
|
||||||
|
|
||||||
|
final JsonObject jsonObject = clientsArray.get(i).getAsJsonObject();
|
||||||
|
if (jsonObject.has(TransferApiConstants.idParamName)) {
|
||||||
|
final Long id = jsonObject.get(TransferApiConstants.idParamName).getAsLong();
|
||||||
|
final Client client = this.clientRepository.findOneWithNotFoundDetection(id);
|
||||||
|
clients.add(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user