diff --git a/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql b/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql
new file mode 100644
index 000000000..485010005
--- /dev/null
+++ b/mifosng-db/migrations/core_db/V104__permissions_for_transfers.sql
@@ -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);
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
index 3a809e0c9..90ff1c732 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/domain/CommandWrapper.java
@@ -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");
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
index caa102376..49d7838ad 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java
@@ -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";
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
index a70f704d1..b8822b31c 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/SynchronousCommandProcessingService.java
@@ -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()) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
index a6f66c1f9..0e9c76e28 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java
@@ -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)
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java
new file mode 100644
index 000000000..7c660cb0c
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/office/domain/OfficeRepositoryWrapper.java
@@ -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;
+
+/**
+ *
+ * Wrapper for {@link OfficeRepository} that adds NULL checking and Error
+ * handling capabilities
+ *
+ */
+@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);
+ }
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java
index fe7fd2553..1a3a3b4c9 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java
@@ -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 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 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 findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("clientId") Long clientId,
+ @Param("groupId") Long groupId);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java
index a760e9e6d..4fc537a27 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java
@@ -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",
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java
index 085ea3d0d..aaf9fca4b 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/domain/Client.java
@@ -429,6 +429,10 @@ public final class Client extends AbstractPersistable {
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 {
public void assignStaff(final Staff staff) {
this.staff = staff;
}
+
+ public Set getGroups(){
+ return this.groups;
+ }
public void close(final Long closureReasonId, final Date closureDate) {
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java
new file mode 100644
index 000000000..465421c46
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientHasBeenClosedException.java
@@ -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");
+ }
+
+}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
index bd7270965..a97f2bbec 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java
@@ -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);
}
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java
index 33119a77b..c46c238a1 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupsApiResource.java
@@ -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" });
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
index dda71b4bc..b88a003ce 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java
@@ -1113,12 +1113,12 @@ public class Loan extends AbstractPersistable {
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 {
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 {
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 {
* details/state.
*/
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
- final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
- final List holidays, final WorkingDays workingDays) {
+ final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
+ final boolean isHolidayEnabled, final List 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 {
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 {
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 {
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 {
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List 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 {
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 {
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 {
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 {
}
- public void updateGroup(Group group) {
- this.group = group;
- }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java
index a084795b4..07854f45b 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java
@@ -52,6 +52,8 @@ public interface LoanRepository extends JpaRepository, 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 getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
@Param("loanType") Integer loanType);
@@ -101,15 +103,21 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat
List getLoansDisbursedAfter(@Param("disbursalDate") Date disbursalDate);
@Query("from Loan loan where loan.client.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
- List findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, @Param("loanStatuses") Collection loanStatuses);
-
- @Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
- List findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds, @Param("loanStatuses") Collection loanStatuses);
+ List findByClientOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds,
+ @Param("loanStatuses") Collection loanStatuses);
+ @Query("from Loan loan where loan.group.office.id IN :officeIds and loan.loanStatus IN :loanStatuses")
+ List findByGroupOfficeIdsAndLoanStatus(@Param("officeIds") Collection officeIds,
+ @Param("loanStatuses") Collection loanStatuses);
+
+ /*** FIXME: Add more appropriate names for the query ***/
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_CLIENT)
List findActiveLoansLoanProductIdsByClient(@Param("clientId") Long clientId, @Param("loanStatus") Integer loanStatus);
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
List findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus);
+ @Query(DOES_CLIENT_HAVE_ACTIVE_LOANS)
+ boolean doesClientHaveActiveLoans(@Param("clientId") Long clientId);
+
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
index abf9b99a9..958a551f2 100644
--- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanStatus.java
@@ -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();
+ }
}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java
new file mode 100644
index 000000000..9b6a6221d
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/api/TransferApiConstants.java
@@ -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 TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName,
+ dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName,
+ transferActiveLoans));
+
+ public static final Set TRANSFER_CLIENT_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName,
+ dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans));
+
+}
\ No newline at end of file
diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java
new file mode 100644
index 000000000..a71646c74
--- /dev/null
+++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/data/TransfersDataValidator.java
@@ -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 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