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);
|
||||
@@ -551,6 +551,10 @@ public class CommandWrapper {
|
||||
public boolean isUpdateGroupRole() {
|
||||
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("UPDATEROLE");
|
||||
}
|
||||
|
||||
public boolean isTransferClientsBetweenGroups() {
|
||||
return this.entityName.equalsIgnoreCase("GROUP") && this.actionName.equalsIgnoreCase("TRANSFERCLIENTS");
|
||||
}
|
||||
|
||||
public boolean isClientAssignStaff() {
|
||||
return this.actionName.equalsIgnoreCase("ASSIGNSTAFF") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||
@@ -559,6 +563,10 @@ public class CommandWrapper {
|
||||
public boolean isClientClose() {
|
||||
return this.actionName.equalsIgnoreCase("CLOSE") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||
}
|
||||
|
||||
public boolean isClientTransfer() {
|
||||
return this.actionName.equalsIgnoreCase("TRANSFER") && this.entityName.equalsIgnoreCase("CLIENT");
|
||||
}
|
||||
|
||||
public boolean isProductMixResource() {
|
||||
return this.entityName.equalsIgnoreCase("PRODUCTMIX");
|
||||
|
||||
+18
@@ -351,6 +351,15 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/clients/" + clientId + "?command=close&template=true";
|
||||
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) {
|
||||
this.actionName = "UPDATE";
|
||||
@@ -1000,6 +1009,15 @@ public class CommandWrapperBuilder {
|
||||
this.href = "/groups/" + groupId + "?command=disassociateClients";
|
||||
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) {
|
||||
this.actionName = "UNASSIGNSTAFF";
|
||||
|
||||
+5
-1
@@ -170,6 +170,8 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("assignClientStaffCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isClientClose()) {
|
||||
handler = this.applicationContext.getBean("closeClientCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isClientTransfer()) {
|
||||
handler = this.applicationContext.getBean("transferClientBetweenBranchesCommandHandler", NewCommandSourceHandler.class);
|
||||
} else {
|
||||
throw new UnsupportedCommandException(wrapper.commandName());
|
||||
}
|
||||
@@ -452,7 +454,9 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
|
||||
handler = this.applicationContext.getBean("updateGroupRoleCommandHandler", NewCommandSourceHandler.class);
|
||||
} else if (wrapper.isAssignStaff()) {
|
||||
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());
|
||||
}
|
||||
} else if (wrapper.isCenterResource()) {
|
||||
|
||||
+1
@@ -128,6 +128,7 @@ public class DataValidatorBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/***FIXME: Vishwas, why does this method have a parameter? Seems wrong***/
|
||||
public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) {
|
||||
if (!trueOfFalseFieldProvided && !ignoreNullValue) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -6,6 +6,7 @@
|
||||
package org.mifosplatform.portfolio.calendar.domain;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
@@ -18,8 +19,15 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
|
||||
|
||||
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")
|
||||
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") 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")) {
|
||||
commandRequest = builder.closeClient(clientId).build();
|
||||
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",
|
||||
|
||||
@@ -429,6 +429,10 @@ public final class Client extends AbstractPersistable<Long> {
|
||||
public Office getOffice() {
|
||||
return this.office;
|
||||
}
|
||||
|
||||
public void updateOffice(final Office office) {
|
||||
this.office = office;
|
||||
}
|
||||
|
||||
private Long staffId() {
|
||||
Long staffId = null;
|
||||
@@ -453,6 +457,10 @@ public final class Client extends AbstractPersistable<Long> {
|
||||
public void assignStaff(final Staff staff) {
|
||||
this.staff = staff;
|
||||
}
|
||||
|
||||
public Set<Group> getGroups(){
|
||||
return this.groups;
|
||||
}
|
||||
|
||||
|
||||
public void close(final Long closureReasonId, final Date closureDate) {
|
||||
|
||||
+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);
|
||||
if (staffId != null) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -329,6 +329,10 @@ public class GroupsApiResource {
|
||||
final CommandWrapper commandRequest = builder.updateRole(groupId, roleId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
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 {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam, new Object[] { "activate", "generateCollectionSheet",
|
||||
"saveCollectionSheet", "unassignStaff", "assignRole", "unassignRole", "updateassignRole" });
|
||||
|
||||
+24
-22
@@ -1113,12 +1113,12 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
for (final LoanCharge loanCharge : setOfLoanCharges()) {
|
||||
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);
|
||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||
}
|
||||
|
||||
|
||||
private LocalDate determineExpectedMaturityDate() {
|
||||
final int numberOfInstallments = this.repaymentScheduleInstallments.size();
|
||||
return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate();
|
||||
@@ -1323,7 +1323,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
|
||||
|
||||
this.actualDisbursementDate = actualDisbursementDate.toDate();
|
||||
this.disbursedBy = currentUser;
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
@@ -1332,14 +1332,15 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
actualChanges.put("dateFormat", command.dateFormat());
|
||||
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);
|
||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||
|
||||
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate, txnExternalId);
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||
holidays, workingDays);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -1366,8 +1367,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
* details/state.
|
||||
*/
|
||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||
@@ -1386,7 +1387,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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
|
||||
final LoanTransaction disbursementTransaction = LoanTransaction.disbursement(this.loanRepaymentScheduleDetail.getPrincipal(),
|
||||
paymentDetail, disbursedOn, txnExternalId);
|
||||
@@ -1511,7 +1513,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
|
||||
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
|
||||
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
@@ -1875,7 +1877,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
final LocalDate closureDate = command.localDateValueOfParameterNamed("transactionDate");
|
||||
final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId");
|
||||
|
||||
|
||||
this.closedOnDate = closureDate.toDate();
|
||||
changes.put("closedOnDate", command.stringValueOfParameterNamed("transactionDate"));
|
||||
|
||||
@@ -2387,7 +2389,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
//first repayment's from date is same as disbursement date.
|
||||
LocalDate tmpFromDate = this.getDisbursementDate();
|
||||
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) {
|
||||
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";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage,
|
||||
getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2494,12 +2496,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
if (!allowTransactionsOnHoliday) {
|
||||
if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage,
|
||||
getExpectedDisbursedOnLocalDate());
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||
final boolean allowTransactionsOnNonWorkingDay) {
|
||||
if (!allowTransactionsOnNonWorkingDay) {
|
||||
@@ -2518,12 +2519,16 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
throw new LoanApplicationDateException("repayment.date.on.holiday", errorMessage, repaymentDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Group group() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public void updateGroup(Group newGroup) {
|
||||
this.group = newGroup;
|
||||
}
|
||||
|
||||
public Integer getCurrentLoanCounter() {
|
||||
return this.loanCounter;
|
||||
}
|
||||
@@ -2564,8 +2569,5 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
}
|
||||
|
||||
public void updateGroup(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
+12
-4
@@ -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 "
|
||||
+ "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)
|
||||
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
|
||||
@Param("loanType") Integer loanType);
|
||||
@@ -101,15 +103,21 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
||||
List<Long> getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
|
||||
|
||||
@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);
|
||||
|
||||
@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> 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")
|
||||
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)
|
||||
List<Long> findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus);
|
||||
|
||||
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
|
||||
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() {
|
||||
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