From d93f630c9bd357c3cef23de52ad1488297a9c3ec Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Wed, 4 Sep 2013 08:20:22 +0530 Subject: [PATCH] cleaning up transfers functionality and prepping for 1.9 release --- api-docs/apiLive.htm | 146 ++++++++++-- .../V108__client_has_transfer_office.sql | 9 + .../service/CommandWrapperBuilder.java | 2 +- .../core/service/DateUtils.java | 8 +- .../domain/CalendarInstanceRepository.java | 2 +- .../client/api/ClientApiConstants.java | 13 +- .../client/api/ClientsApiResource.java | 2 + .../portfolio/client/data/ClientData.java | 51 ++-- .../portfolio/client/domain/Client.java | 39 ++- .../ClientReadPlatformServiceImpl.java | 44 ++-- .../group/api/GroupsApiResource.java | 39 ++- .../portfolio/group/domain/Group.java | 8 + .../service/GroupReadPlatformService.java | 2 +- .../service/GroupReadPlatformServiceImpl.java | 8 +- .../loanaccount/domain/LoanRepository.java | 6 +- .../loanaccount/domain/LoanTransaction.java | 7 + .../service/LoanReadPlatformServiceImpl.java | 2 +- .../service/LoanWritePlatformService.java | 9 +- ...WritePlatformServiceJpaRepositoryImpl.java | 55 +++-- .../portfolio/note/domain/Note.java | 2 +- .../service/NoteWritePlatformService.java | 3 + ...WritePlatformServiceJpaRepositoryImpl.java | 10 + .../domain/SavingsAccountRepository.java | 6 +- .../transfer/api/TransferApiConstants.java | 14 +- .../transfer/data/TransfersDataValidator.java | 85 +++++-- ...tNotAwaitingTransferApprovalException.java | 2 +- ...tingTransferApprovalOrOnHoldException.java | 12 + .../TransferNotSupportedException.java | 62 ++--- ...WritePlatformServiceJpaRepositoryImpl.java | 225 ++++++++++++------ .../main/resources/META-INF/persistence.xml | 1 + 30 files changed, 610 insertions(+), 264 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V108__client_has_transfer_office.sql rename mifosng-provider/src/main/java/org/mifosplatform/portfolio/{client => transfer}/exception/ClientNotAwaitingTransferApprovalException.java (88%) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index eeb1df727..7d358af1d 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -90,7 +90,7 @@
- +

Overview

@@ -209,8 +209,32 @@ - clients/{clientId}?command=transfer - Transfer Client + clients/{clientId}?command=proposeTransfer + Propose a Client Transfer + + + + + + + clients/{clientId}?command=withdrawTransfer + Withdraw Client Transfer Proposal + + + + + + + clients/{clientId}?command=acceptTransfer + Accept Client Transfer + + + + + + + clients/{clientId}?command=rejectTransfer + Reject Client Transfer @@ -586,7 +610,7 @@ groups/{groupId}?command=transferClients - Transfer Clients + Bulk Transfer Clients across Groups @@ -2138,19 +2162,20 @@ Request Body:
-   +  
-

Transfer a Client

-

Allows you to transfer a Client between Offices.

+

Propose a Client Transfer

+

Allows you to propose the transfer of a Client to a different Offices.

- POST https://Domain Name/api/v1/clients/{clientId}?command=transfer - POST clients/1?command=transfer + POST https://Domain Name/api/v1/clients/{clientId}?command=proposeTransfer + POST clients/1?command=proposeTransfer Content-Type: application/json Request Body: { - destinationOfficeId:1 + "destinationOfficeId":"2", + "note":"Client Relocating to Bangalore" } @@ -2162,7 +2187,92 @@ Request Body:
-
+
+ +   +
+
+

Withdraw a Client Transfer

+

Allows you to withdraw the proposed the transfer of a Client to a different Offices.

+

Withdrawal can happen only if the destination Branch (to which the transfer was + proposed) has not already + accepted the transfer proposal

+
+
+ POST https://Domain Name/api/v1/clients/{clientId}?command=withdrawTransfer + POST clients/1?command=withdrawTransfer +Content-Type: application/json +Request Body: +{ + "note":"Sorry, data entry error" +} + + +{ + "clientId": 2, + "resourceId": 2 +} + + + +
+
+ +   +
+
+

Reject a Client Transfer

+

Allows the Destination Branch to reject the proposed Client Transfer.

+
+
+ POST https://Domain Name/api/v1/clients/{clientId}?command=rejectTransfer + POST clients/1?command=rejectTransfer +Content-Type: application/json +Request Body: +{ + "note":"We cannot accept tranfers of clients having loans with less than 1 repayment left" +} + + +{ + "clientId": 2, + "resourceId": 2 +} + + + +
+
+ +   +
+
+

Reject a Client Transfer

+

Allows the Destination Branch to accept the proposed Client Transfer.

+

The destination branch may also choose to link this client to a group (in which case, any existing active JLG loan of the client is rescheduled to match the meeting frequency of the group) and loan Officer at the time of accepting + the transfer

+
+
+ POST https://Domain Name/api/v1/clients/{clientId}?command=acceptTransfer + POST clients/1?command=acceptTransfer +Content-Type: application/json +Request Body: +{ + "note":"Due Diligence done and all documents received" +} + + +{ + "clientId": 2, + "resourceId": 2 +} + + + +
+
+ +  
@@ -3866,7 +3976,8 @@ Request Body: destinationGroupId - The identifier of the group to which the clients are to be transferred. + The identifier of the group to which the clients are to be transferred ( + has to be in the same branch as the source Group). clients @@ -3883,15 +3994,6 @@ Request Body: should be linked to the assigned loan officer for the group - - transferActiveLoans - - - Flag specifies if transfers should proceed when active loans are encountered. - If set to true, the schedules of the actives loans are updated to match that meeting schedule - of the destination Group - -
@@ -3911,8 +4013,6 @@ Request Body:
inheritDestinationGroupLoanOfficer (defaults to true), transferActiveLoans (defaults to true)
- -
diff --git a/mifosng-db/migrations/core_db/V108__client_has_transfer_office.sql b/mifosng-db/migrations/core_db/V108__client_has_transfer_office.sql new file mode 100644 index 000000000..9223efb10 --- /dev/null +++ b/mifosng-db/migrations/core_db/V108__client_has_transfer_office.sql @@ -0,0 +1,9 @@ +/***Store destination office Id while client is pending transfers and effective joining date in a particular branch**/ +ALTER TABLE `m_client` + ADD COLUMN `office_joining_date` DATE NULL AFTER `activation_date`, + ADD COLUMN `transfer_to_office_id` BIGINT(20) NULL AFTER `office_id`, + ADD CONSTRAINT `FK_m_client_m_office` FOREIGN KEY (`transfer_to_office_id`) REFERENCES `m_office` (`id`); + + +/**For current Clients, set the office joining date to activation date**/ +update m_client set office_joining_date=activation_date; 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 76996c137..fad90439d 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 @@ -363,7 +363,7 @@ public class CommandWrapperBuilder { } public CommandWrapperBuilder withdrawClientTransferRequest(final Long clientId) { - this.actionName = "PROPOSETRANSFER"; + this.actionName = "WITHDRAWTRANSFER"; this.entityName = "CLIENT"; this.entityId = clientId; this.clientId = clientId; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java index 19313290d..9b06e6496 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/service/DateUtils.java @@ -22,6 +22,10 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation public class DateUtils { + public static Date getDateOfTenant() { + return getLocalDateOfTenant().toDateMidnight().toDate(); + } + public static LocalDate getLocalDateOfTenant() { LocalDate today = new LocalDate(); @@ -53,8 +57,8 @@ public class DateUtils { dataValidationErrors); } } - - public static String formatToSqlDate(final Date date){ + + public static String formatToSqlDate(final Date date) { final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); final String formattedSqlDate = df.format(date); return formattedSqlDate; 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 1a3a3b4c9..baddabbd3 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 @@ -27,7 +27,7 @@ public interface CalendarInstanceRepository extends JpaRepository 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") + @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/ClientApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java index e7420e4b6..3b4235520 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientApiConstants.java @@ -30,6 +30,7 @@ public class ClientApiConstants { public static final String lastnameParamName = "lastname"; public static final String fullnameParamName = "fullname"; public static final String officeIdParamName = "officeId"; + public static final String transferOfficeIdParamName = "transferOfficeIdParamName"; public static final String activeParamName = "active"; public static final String activationDateParamName = "activationDate"; public static final String staffIdParamName = "staffId"; @@ -40,6 +41,7 @@ public class ClientApiConstants { public static final String hierarchyParamName = "hierarchy"; public static final String displayNameParamName = "displayName"; public static final String officeNameParamName = "officeName"; + public static final String trasnferOfficeNameParamName = "transferOfficeName"; public static final String imageKeyParamName = "imageKey"; public static final String imagePresentParamName = "imagePresent"; @@ -55,8 +57,8 @@ public class ClientApiConstants { lastnameParamName, fullnameParamName, officeIdParamName, activeParamName, activationDateParamName, staffIdParamName)); public static final Set CLIENT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, - dateFormatParamName, accountNoParamName, externalIdParamName, firstnameParamName, middlenameParamName, - lastnameParamName, fullnameParamName, activeParamName, activationDateParamName, staffIdParamName)); + dateFormatParamName, accountNoParamName, externalIdParamName, firstnameParamName, middlenameParamName, lastnameParamName, + fullnameParamName, activeParamName, activationDateParamName, staffIdParamName)); /** * These parameters will match the class level parameters of @@ -65,12 +67,13 @@ public class ClientApiConstants { */ public static final Set CLIENT_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, accountNoParamName, externalIdParamName, statusParamName, activeParamName, activationDateParamName, firstnameParamName, middlenameParamName, - lastnameParamName, fullnameParamName, displayNameParamName, officeIdParamName, officeNameParamName, hierarchyParamName, - imageKeyParamName, imagePresentParamName, groupsParamName, officeOptionsParamName, staffOptionsParamName)); + lastnameParamName, fullnameParamName, displayNameParamName, officeIdParamName, officeNameParamName, transferOfficeIdParamName, + trasnferOfficeNameParamName, hierarchyParamName, imageKeyParamName, imagePresentParamName, groupsParamName, + officeOptionsParamName, staffOptionsParamName)); public static final Set ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, activationDateParamName)); - + public static final Set CLIENT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, closureDateParamName, closureReasonIdParamName)); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/api/ClientsApiResource.java index beaa7792b..56da375f4 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 @@ -86,6 +86,8 @@ public class ClientsApiResource { context.authenticatedUser().validateHasReadPermission(ClientApiConstants.CLIENT_RESOURCE_NAME); if (is(commandParam, "close")) { clientData = this.clientReadPlatformService.retrieveAllClosureReasons(ClientApiConstants.CLIENT_CLOSURE_REASON); + } else if (is(commandParam, "acceptTransfer")) { + clientData = this.clientReadPlatformService.retrieveAllClosureReasons(ClientApiConstants.CLIENT_CLOSURE_REASON); } else { clientData = this.clientReadPlatformService.retrieveTemplate(officeId, staffInSelectedOfficeOnly); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java index 41f0564e0..39295e40d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientData.java @@ -40,6 +40,8 @@ final public class ClientData implements Comparable { private final Long officeId; private final String officeName; + private final Long transferToOfficeId; + private final String transferToOfficeName; private final Long imageId; private final Boolean imagePresent; @@ -57,49 +59,52 @@ final public class ClientData implements Comparable { public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection officeOptions, final Collection staffOptions, final Collection closureReasons) { - return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, joinedDate, null, null, null, - officeOptions, null, staffOptions, closureReasons); + return new ClientData(null, null, officeId, null, null, null, null, null, null, null, null, null, null, joinedDate, null, null, + null, officeOptions, null, staffOptions, closureReasons); } public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) { - return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName, clientData.id, - clientData.firstname, clientData.middlename, clientData.lastname, clientData.fullname, clientData.displayName, - clientData.externalId, clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, - templateData.officeOptions, clientData.groups, templateData.staffOptions, null); + return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName, + clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename, + clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.activationDate, + clientData.imageId, clientData.staffId, clientData.staffName, templateData.officeOptions, clientData.groups, + templateData.staffOptions, null); } public static ClientData setParentGroups(final ClientData clientData, final Collection parentGroups) { - return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName, clientData.id, - clientData.firstname, clientData.middlename, clientData.lastname, clientData.fullname, clientData.displayName, - clientData.externalId, clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, - clientData.officeOptions, parentGroups, clientData.staffOptions, null); + return new ClientData(clientData.accountNo, clientData.status, clientData.officeId, clientData.officeName, + clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename, + clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.activationDate, + clientData.imageId, clientData.staffId, clientData.staffName, clientData.officeOptions, parentGroups, + clientData.staffOptions, null); } public static ClientData clientIdentifier(final Long id, final String accountNo, final EnumOptionData status, final String firstname, final String middlename, final String lastname, final String fullname, final String displayName, final Long officeId, final String officeName) { - return new ClientData(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, null, - null, null, null, null, null, null, null, null); + return new ClientData(accountNo, status, officeId, officeName, null, null, id, firstname, middlename, lastname, fullname, + displayName, null, null, null, null, null, null, null, null, null); } public static ClientData lookup(final Long id, final String displayName, final Long officeId, final String officeName) { - return new ClientData(null, null, officeId, officeName, id, null, null, null, null, displayName, null, null, null, null, null, - null, null, null, null); + return new ClientData(null, null, officeId, officeName, null, null, id, null, null, null, null, displayName, null, null, null, + null, null, null, null, null, null); } public static ClientData instance(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, - final Long id, final String firstname, final String middlename, final String lastname, final String fullname, - final String displayName, final String externalId, final LocalDate activationDate, final Long imageId, final Long staffId, - final String staffName) { - return new ClientData(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, - externalId, activationDate, imageId, staffId, staffName, null, null, null, null); + final Long transferToOfficeId, final String transferToOfficeName, final Long id, final String firstname, + final String middlename, final String lastname, final String fullname, final String displayName, final String externalId, + final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName) { + return new ClientData(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, middlename, + lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName, null, null, null, null); } - private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, final Long id, - final String firstname, final String middlename, final String lastname, final String fullname, final String displayName, - final String externalId, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName, + private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, + final Long transferToOfficeId, final String transferToOfficeName, final Long id, final String firstname, + final String middlename, final String lastname, final String fullname, final String displayName, final String externalId, + final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName, final Collection allowedOffices, final Collection groups, final Collection staffOptions, final Collection closureReasons) { this.accountNo = accountNo; @@ -111,6 +116,8 @@ final public class ClientData implements Comparable { } this.officeId = officeId; this.officeName = officeName; + this.transferToOfficeId = transferToOfficeId; + this.transferToOfficeName = transferToOfficeName; this.id = id; this.firstname = StringUtils.defaultIfEmpty(firstname, null); this.middlename = StringUtils.defaultIfEmpty(middlename, null); 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 1c4dc756b..b66e308c5 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 @@ -55,6 +55,10 @@ public final class Client extends AbstractPersistable { @JoinColumn(name = "office_id", nullable = false) private Office office; + @ManyToOne + @JoinColumn(name = "transfer_to_office_id", nullable = true) + private Office transferToOffice; + @OneToOne(optional = true) @JoinColumn(name = "image_id", nullable = true) private Image image; @@ -69,6 +73,10 @@ public final class Client extends AbstractPersistable { @Temporal(TemporalType.DATE) private Date activationDate; + @Column(name = "office_joining_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date officeJoiningDate; + @Column(name = "firstname", length = 50) private String firstname; @@ -123,13 +131,15 @@ public final class Client extends AbstractPersistable { } LocalDate activationDate = null; + LocalDate officeJoiningDate = null; if (active) { status = ClientStatus.ACTIVE; activationDate = command.localDateValueOfParameterNamed(ClientApiConstants.activationDateParamName); + officeJoiningDate = activationDate; } return new Client(status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname, activationDate, - externalId, staff); + officeJoiningDate, externalId, staff); } protected Client() { @@ -138,7 +148,7 @@ public final class Client extends AbstractPersistable { private Client(final ClientStatus status, final Office office, final Group clientParentGroup, final String accountNo, final String firstname, final String middlename, final String lastname, final String fullname, final LocalDate activationDate, - final String externalId, final Staff staff) { + final LocalDate officeJoiningDate, final String externalId, final Staff staff) { if (StringUtils.isBlank(accountNo)) { this.accountNumber = new RandomPasswordGenerator(19).generate(); this.accountNumberRequiresAutoGeneration = true; @@ -155,6 +165,9 @@ public final class Client extends AbstractPersistable { if (activationDate != null) { this.activationDate = activationDate.toDateMidnight().toDate(); } + if (officeJoiningDate != null) { + this.officeJoiningDate = officeJoiningDate.toDateMidnight().toDate(); + } if (StringUtils.isNotBlank(firstname)) { this.firstname = firstname.trim(); } else { @@ -243,6 +256,7 @@ public final class Client extends AbstractPersistable { } this.activationDate = activationLocalDate.toDate(); + this.officeJoiningDate = activationDate; this.status = ClientStatus.ACTIVE.getValue(); validate(); @@ -260,6 +274,14 @@ public final class Client extends AbstractPersistable { return ClientStatus.fromInt(this.status).isTransferInProgress(); } + public boolean isTransferOnHold() { + return ClientStatus.fromInt(this.status).isTransferOnHold(); + } + + public boolean isTransferInProgressOrOnHold() { + return isTransferInProgress() || isTransferOnHold(); + } + public boolean isNotPending() { return !isPending(); } @@ -334,6 +356,7 @@ public final class Client extends AbstractPersistable { final LocalDate newValue = command.localDateValueOfParameterNamed(ClientApiConstants.activationDateParamName); this.activationDate = newValue.toDate(); + this.officeJoiningDate = activationDate; } validate(); @@ -437,10 +460,22 @@ public final class Client extends AbstractPersistable { return this.office; } + public Office getTransferToOffice() { + return this.transferToOffice; + } + public void updateOffice(final Office office) { this.office = office; } + public void updateTransferToOffice(final Office office) { + this.transferToOffice = office; + } + + public void updateOfficeJoiningDate(final Date date) { + this.officeJoiningDate = date; + } + private Long staffId() { Long staffId = null; if (this.staff != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java index 8eb05e30a..15d840193 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientReadPlatformServiceImpl.java @@ -100,7 +100,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService StringBuilder sqlBuilder = new StringBuilder(200); sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); sqlBuilder.append(this.clientMapper.schema()); - sqlBuilder.append(" where o.hierarchy like ?"); + sqlBuilder.append(" where o.hierarchy like ? or transferToOffice.hierarchy like ?"); final String extraCriteria = buildSqlStringFromClientCriteria(searchParameters); @@ -124,8 +124,8 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService } final String sqlCountRows = "SELECT FOUND_ROWS()"; - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), - new Object[] { hierarchySearchString }, this.clientMapper); + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), new Object[] { + hierarchySearchString, hierarchySearchString }, this.clientMapper); } private String buildSqlStringFromClientCriteria(final SearchParameters searchParameters) { @@ -184,9 +184,10 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService String hierarchy = currentUser.getOffice().getHierarchy(); String hierarchySearchString = hierarchy + "%"; - String sql = "select " + this.clientMapper.schema() + " where o.hierarchy like ? and c.id = ?"; - ClientData clientData = this.jdbcTemplate.queryForObject(sql, this.clientMapper, - new Object[] { hierarchySearchString, clientId }); + String sql = "select " + this.clientMapper.schema() + + " where ( o.hierarchy like ? or transferToOffice.hierarchy like ?) and c.id = ?"; + ClientData clientData = this.jdbcTemplate.queryForObject(sql, this.clientMapper, new Object[] { hierarchySearchString, + hierarchySearchString, clientId }); String clientGroupsSql = "select " + this.clientGroupsMapper.parentGroupsSchema(); @@ -255,6 +256,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService sqlBuilder.append("c.id as id, c.account_no as accountNo, c.external_id as externalId, c.status_enum as statusEnum, "); sqlBuilder.append("c.office_id as officeId, o.name as officeName, "); + sqlBuilder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, "); sqlBuilder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, "); sqlBuilder.append("c.fullname as fullname, c.display_name as displayName, "); sqlBuilder.append("c.activation_date as activationDate, c.image_id as imageId, "); @@ -263,6 +265,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService sqlBuilder.append("join m_office o on o.id = c.office_id "); sqlBuilder.append("join m_group_client pgc on pgc.client_id = c.id "); sqlBuilder.append("left join m_staff s on s.id = c.staff_id "); + sqlBuilder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id "); this.schema = sqlBuilder.toString(); } @@ -280,6 +283,11 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final EnumOptionData status = ClientEnumerations.status(statusEnum); final Long officeId = JdbcSupport.getLong(rs, "officeId"); + final String officeName = rs.getString("officeName"); + + final Long transferToOfficeId = JdbcSupport.getLong(rs, "transferToOfficeId"); + final String transferToOfficeName = rs.getString("transferToOfficeName"); + final Long id = JdbcSupport.getLong(rs, "id"); final String firstname = rs.getString("firstname"); final String middlename = rs.getString("middlename"); @@ -289,12 +297,11 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final String externalId = rs.getString("externalId"); final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate"); final Long imageId = JdbcSupport.getLong(rs, "imageId"); - final String officeName = rs.getString("officeName"); final Long staffId = JdbcSupport.getLong(rs, "staffId"); final String staffName = rs.getString("staffName"); - return ClientData.instance(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, - externalId, activationDate, imageId, staffId, staffName); + return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, + middlename, lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName); } } @@ -305,13 +312,14 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final String hierarchy = currentUser.getOffice().getHierarchy(); final String hierarchySearchString = hierarchy + "%"; - final String sql = "select " + this.membersOfGroupMapper.schema() + final String sql = "select " + + this.membersOfGroupMapper.schema() + " left join m_group g on pgc.group_id=g.id where o.hierarchy like ? and g.parent_id = ? and c.status_enum = ? group by c.id"; return this.jdbcTemplate.query(sql, this.membersOfGroupMapper, new Object[] { hierarchySearchString, centerId, ClientStatus.ACTIVE.getValue() }); } - + private static final class ClientMapper implements RowMapper { private final String schema; @@ -321,7 +329,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService builder.append("c.id as id, c.account_no as accountNo, c.external_id as externalId, c.status_enum as statusEnum, "); builder.append("c.office_id as officeId, o.name as officeName, "); - builder.append("c.office_id as officeId, o.name as officeName, "); + builder.append("c.transfer_to_office_id as transferToOfficeId, transferToOffice.name as transferToOfficeName, "); builder.append("c.firstname as firstname, c.middlename as middlename, c.lastname as lastname, "); builder.append("c.fullname as fullname, c.display_name as displayName, "); builder.append("c.activation_date as activationDate, c.image_id as imageId, "); @@ -329,7 +337,7 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService builder.append("from m_client c "); builder.append("join m_office o on o.id = c.office_id "); builder.append("left join m_staff s on s.id = c.staff_id "); - + builder.append("left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id "); this.schema = builder.toString(); } @@ -346,6 +354,11 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final EnumOptionData status = ClientEnumerations.status(statusEnum); final Long officeId = JdbcSupport.getLong(rs, "officeId"); + final String officeName = rs.getString("officeName"); + + final Long transferToOfficeId = JdbcSupport.getLong(rs, "transferToOfficeId"); + final String transferToOfficeName = rs.getString("transferToOfficeName"); + final Long id = JdbcSupport.getLong(rs, "id"); final String firstname = rs.getString("firstname"); final String middlename = rs.getString("middlename"); @@ -354,13 +367,12 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final String displayName = rs.getString("displayName"); final String externalId = rs.getString("externalId"); final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate"); - final String officeName = rs.getString("officeName"); final Long imageId = JdbcSupport.getLong(rs, "imageId"); final Long staffId = JdbcSupport.getLong(rs, "staffId"); final String staffName = rs.getString("staffName"); - return ClientData.instance(accountNo, status, officeId, officeName, id, firstname, middlename, lastname, fullname, displayName, - externalId, activationDate, imageId, staffId, staffName); + return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, + middlename, lastname, fullname, displayName, externalId, activationDate, imageId, staffId, staffName); } } 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 3c2b8a5e6..286f70f05 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 @@ -5,7 +5,6 @@ */ package org.mifosplatform.portfolio.group.api; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -143,22 +142,11 @@ public class GroupsApiResource { @QueryParam("officeId") final Long officeId, @QueryParam("externalId") final String externalId, @QueryParam("name") final String name, @QueryParam("underHierarchy") final String hierarchy, @QueryParam("offset") final Integer offset, @QueryParam("limit") final Integer limit, - @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder, - @QueryParam("templateType") final String templateType, @QueryParam("groupId") final Long groupId) { + @QueryParam("orderBy") final String orderBy, @QueryParam("sortOrder") final String sortOrder) { this.context.authenticatedUser().validateHasReadPermission(GroupingTypesApiConstants.GROUP_RESOURCE_NAME); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); - if (templateType != null && templateType.equalsIgnoreCase("clientstransfertemplate")) { - final Collection groups; - if (officeId != null && groupId != null) { - groups = this.groupReadPlatformService.retrieveGroupsForLookup(officeId, groupId); - } else { - groups = new ArrayList(); - } - return this.groupGeneralApiJsonSerializer.serialize(settings, groups, GroupingTypesApiConstants.GROUP_RESPONSE_DATA_PARAMETERS); - } - final SearchParameters searchParameters = SearchParameters.forGroups(sqlSearch, officeId, externalId, name, hierarchy, offset, limit, orderBy, sortOrder); final Page groups = this.groupReadPlatformService.retrieveAll(searchParameters); @@ -186,7 +174,7 @@ public class GroupsApiResource { GroupRoleData selectedRole = null; Collection calendars = null; CalendarData collectionMeetingCalendar = null; - + if (!associationParameters.isEmpty()) { if (associationParameters.contains("all")) { associationParameters.addAll(Arrays.asList("clientMembers", "groupRoles", "calendars", "collectionMeetingCalendar")); @@ -204,26 +192,29 @@ public class GroupsApiResource { } } if (associationParameters.contains("parentCalendars")) { - final List calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all"); - calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), calendarTypeOptions); + final List calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all"); + calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), + calendarTypeOptions); if (CollectionUtils.isEmpty(calendars)) { calendars = null; } } if (associationParameters.contains("collectionMeetingCalendar")) { - if(group.isChildGroup()){ - collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(group.getParentId(), CalendarEntityType.CENTERS.getValue()); - }else { - collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(groupId, CalendarEntityType.GROUPS.getValue()); + if (group.isChildGroup()) { + collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(group.getParentId(), + CalendarEntityType.CENTERS.getValue()); + } else { + collectionMeetingCalendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(groupId, + CalendarEntityType.GROUPS.getValue()); } - if(collectionMeetingCalendar != null){ + if (collectionMeetingCalendar != null) { collectionMeetingCalendar = this.calendarReadPlatformService.generateRecurringDate(collectionMeetingCalendar); } } - + group = GroupGeneralData.withAssocations(group, membersOfGroup, groupRoles, calendars, collectionMeetingCalendar); } - + if (roleId != null) { selectedRole = this.groupRolesReadPlatformService.retrieveGroupRole(groupId, roleId); if (selectedRole != null) { @@ -268,7 +259,7 @@ public class GroupsApiResource { return this.toApiJsonSerializer.serialize(result); } - + @PUT @Path("{groupId}") @Consumes({ MediaType.APPLICATION_JSON }) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java index 0fc5d9dcd..d730814af 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/Group.java @@ -387,6 +387,14 @@ public final class Group extends AbstractPersistable { public boolean isTransferInProgress() { return GroupingTypeStatus.fromInt(this.status).isTransferInProgress(); } + + public boolean isTransferOnHold() { + return GroupingTypeStatus.fromInt(this.status).isTransferOnHold(); + } + + public boolean isTransferInProgressOrOnHold() { + return isTransferInProgress() || isTransferOnHold(); + } public boolean isChildClient(final Long clientId) { if (clientId != null && this.clientMembers != null && !this.clientMembers.isEmpty()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java index 0109d693a..9eeb0acab 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformService.java @@ -18,5 +18,5 @@ public interface GroupReadPlatformService { GroupGeneralData retrieveOne(Long groupId); - Collection retrieveGroupsForLookup(Long officeId, Long groupId); + Collection retrieveGroupsForLookup(Long officeId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java index 13922efda..06733d8d9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupReadPlatformServiceImpl.java @@ -206,17 +206,17 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService { } @Override - public Collection retrieveGroupsForLookup(final Long officeId, final Long groupId) { + public Collection retrieveGroupsForLookup(final Long officeId) { this.context.authenticatedUser(); GroupLookupDataMapper rm = new GroupLookupDataMapper(); - String sql = "Select " + rm.schema() + " where g.office_id=? and g.id !=?"; - return this.jdbcTemplate.query(sql, rm, new Object[] { officeId, groupId }); + String sql = "Select " + rm.schema() + " and g.office_id=?"; + return this.jdbcTemplate.query(sql, rm, new Object[] { officeId }); } private static final class GroupLookupDataMapper implements RowMapper { public final String schema() { - return "g.id as id, g.display_name as displayName from m_group g"; + return "g.id as id, g.display_name as displayName from m_group g where g.level_id = 2 "; } @Override 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 e868efaee..a58c10f45 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,7 +52,7 @@ 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,303,304)"; + public static final String DOES_CLIENT_HAVE_NON_CLOSED_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,303,304)"; @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, @@ -117,7 +117,7 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat @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); + @Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS) + boolean doNonClosedLoanAccountsExistForClient(@Param("clientId") Long clientId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 2925a08ed..29e7148d2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -137,6 +137,13 @@ public final class LoanTransaction extends AbstractPersistable { .getTotalPenaltyChargesOutstanding(), false, null); } + public static LoanTransaction withdrawTransfer(final Office office, final Loan loan, final LocalDate transferDate) { + return new LoanTransaction(loan, office, LoanTransactionType.APPROVE_TRANSFER.getValue(), transferDate.toDateMidnight().toDate(), + loan.getSummary().getTotalOutstanding(), loan.getSummary().getTotalPrincipalOutstanding(), loan.getSummary() + .getTotalInterestOutstanding(), loan.getSummary().getTotalFeeChargesOutstanding(), loan.getSummary() + .getTotalPenaltyChargesOutstanding(), false, null); + } + public static LoanTransaction refund(final Office office, final Money amount, final PaymentDetail paymentDetail, final LocalDate paymentDate, final String externalId) { return new LoanTransaction(null, office, LoanTransactionType.REFUND, paymentDetail, amount.getAmount(), paymentDate, externalId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 879c07813..044297758 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -178,7 +178,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { /*** TODO Vishwas: Remove references to "Contra" from the codebase ***/ String sql = "select " + rm.LoanPaymentsSchema() - + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.is_reversed=0 order by tr.transaction_date ASC"; + + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and tr.is_reversed=0 order by tr.transaction_date ASC,id "; return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); } catch (EmptyResultDataAccessException e) { return null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index c43b665de..f8d78d084 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -11,6 +11,8 @@ import java.util.Map; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.organisation.office.domain.Office; +import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; @@ -59,9 +61,10 @@ public interface LoanWritePlatformService { LoanTransaction initiateLoanTransfer(Long accountId, LocalDate TransferDate); - LoanTransaction acceptLoanTransfer(Long accountId, LocalDate TransferDate); - LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate TransferDate); - LoanTransaction rejectLoanTransfer(Long accountId, LocalDate TransferDate); + void rejectLoanTransfer(Long accountId); + + LoanTransaction acceptLoanTransfer(Long accountId, LocalDate TransferDate, Office acceptedInOffice, Staff loanOfficer); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index efcdd8538..d6ed5530e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -53,7 +53,6 @@ import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeWaivedExce import org.mifosplatform.portfolio.charge.exception.LoanChargeNotFoundException; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; -import org.mifosplatform.portfolio.client.exception.ClientNotAwaitingTransferApprovalException; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.mifosplatform.portfolio.collectionsheet.command.SingleDisbursalCommand; @@ -922,15 +921,38 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override - public LoanTransaction acceptLoanTransfer(final Long accountId, final LocalDate TransferDate) { + public LoanTransaction acceptLoanTransfer(final Long accountId, final LocalDate transferDate, final Office acceptedInOffice, + final Staff loanOfficer) { final Loan loan = this.loanAssembler.assembleFrom(accountId); - validateClientOrGroupAwaitingTransferAcceptance(loan); final List existingTransactionIds = new ArrayList(loan.findExistingTransactionIds()); final List existingReversedTransactionIds = new ArrayList(loan.findExistingReversedTransactionIds()); - final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(loan.getOffice(), loan, TransferDate); + final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate); + loan.getLoanTransactions().add(newTransferAcceptanceTransaction); + loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); + if (loanOfficer != null) { + loan.reassignLoanOfficer(loanOfficer, transferDate); + } + + this.loanTransactionRepository.save(newTransferAcceptanceTransaction); + this.loanRepository.save(loan); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + return newTransferAcceptanceTransaction; + } + + @Transactional + @Override + public LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate TransferDate) { + final Loan loan = this.loanAssembler.assembleFrom(accountId); + + final List existingTransactionIds = new ArrayList(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList(loan.findExistingReversedTransactionIds()); + + final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.withdrawTransfer(loan.getOffice(), loan, TransferDate); loan.getLoanTransactions().add(newTransferAcceptanceTransaction); loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); @@ -944,16 +966,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override - public LoanTransaction withdrawLoanTransfer(Long accountId, LocalDate TransferDate) { - // TODO Auto-generated method stub - return null; - } - - @Transactional - @Override - public LoanTransaction rejectLoanTransfer(Long accountId, LocalDate TransferDate) { - // TODO Auto-generated method stub - return null; + public void rejectLoanTransfer(Long accountId) { + final Loan loan = this.loanAssembler.assembleFrom(accountId); + loan.setLoanStatus(LoanStatus.TRANSFER_ON_HOLD.getValue()); + this.loanRepository.save(loan); } @Transactional @@ -1321,15 +1337,4 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - private void validateClientOrGroupAwaitingTransferAcceptance(final Loan loan) { - final Client client = loan.client(); - if (client != null) { - if (!client.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(client.getId()); } - } - final Group group = loan.group(); - if (group != null) { - if (!group.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(group.getId()); } - } - } - } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java index ead9f7cf8..daa5144bb 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/domain/Note.java @@ -76,7 +76,7 @@ public class Note extends AbstractAuditableCustom { return new Note(account, note); } - private Note(final Client client, final String note) { + public Note(final Client client, final String note) { this.client = client; this.note = note; this.noteTypeId = NoteType.CLIENT.getValue(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformService.java index 9660557b9..9539a1352 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformService.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.note.service; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.client.domain.Client; public interface NoteWritePlatformService { @@ -15,4 +16,6 @@ public interface NoteWritePlatformService { CommandProcessingResult updateNote(JsonCommand command); CommandProcessingResult deleteNote(JsonCommand command); + + void createAndPersistClientNote(Client client, JsonCommand command); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java index 0753a6565..818872e62 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.note.service; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -73,6 +74,15 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf } + @Override + public void createAndPersistClientNote(final Client client, final JsonCommand command) { + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + final Note newNote = new Note(client, noteText); + this.noteRepository.save(newNote); + } + } + private CommandProcessingResult createGroupNote(final JsonCommand command) { final Long resourceId = command.getSupportedEntityId(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java index 69c8e3b37..9e65b5d44 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountRepository.java @@ -16,11 +16,13 @@ public interface SavingsAccountRepository extends JpaRepository findSavingAccountByClientId(@Param("clientId") Long clientId); - + @Query("from SavingsAccount s_acc where s_acc.status = :status") List findSavingAccountByStatus(@Param("status") Integer status); - @Query("from SavingsAccount sa where sa.client.id = :clientId and sa.group.id = :groupId") List findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId); + + @Query("select case when (count (saving) > 0) then true else false end from SavingsAccount saving where saving.client.id = :clientId and saving.status in (100,200,300,303,304)") + boolean doNonClosedSavingAccountsExistForClient(@Param("clientId") Long clientId); } \ 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 index fb0857097..d375f17c7 100644 --- 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 @@ -20,15 +20,23 @@ public class TransferApiConstants { 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 newStaffIdParamName = "staffId"; public static final String transferActiveLoans = "transferActiveLoans"; public static final String destinationOfficeIdParamName = "destinationOfficeId"; + public static final String note = "note"; 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 PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet(Arrays.asList( - localeParamName, dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans)); + public static final Set PROPOSE_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, + dateFormatParamName, destinationOfficeIdParamName, transferActiveLoans, note)); + + public static final Set ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet(Arrays.asList(newStaffIdParamName, + destinationGroupIdParamName, note)); + + public static final Set REJECT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet(Arrays.asList(note)); + + public static final Set WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet(Arrays.asList(note)); } \ 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 index bcdb2ac4d..70bb54a5a 100644 --- 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 @@ -63,13 +63,6 @@ public final class TransfersDataValidator { .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); @@ -80,13 +73,12 @@ public final class TransfersDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } - public void validateForProposeClientTransferBetweenBranches(final String json) { + public void validateForProposeClientTransfer(final String json) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final Type typeOfMap = new TypeToken>() {}.getType(); - this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, - TransferApiConstants.PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.PROPOSE_CLIENT_TRANSFER_DATA_PARAMETERS); final JsonElement element = this.fromApiJsonHelper.parse(json); final List dataValidationErrors = new ArrayList(); @@ -99,13 +91,76 @@ public final class TransfersDataValidator { 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).ignoreIfNull() - .integerGreaterThanZero(); - } + validateNote(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); } + public void validateForAcceptClientTransfer(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS); + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ClientApiConstants.CLIENT_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + 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.destinationGroupIdParamName, element)) { + final Long destinationGroupId = this.fromApiJsonHelper.extractLongNamed(TransferApiConstants.destinationGroupIdParamName, + element); + baseDataValidator.reset().parameter(TransferApiConstants.destinationGroupIdParamName).value(destinationGroupId).notNull() + .integerGreaterThanZero(); + } + + validateNote(baseDataValidator, element); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForRejectClientTransfer(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.REJECT_CLIENT_TRANSFER_DATA_PARAMETERS); + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ClientApiConstants.CLIENT_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + validateNote(baseDataValidator, element); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForWithdrawClientTransfer(final String json) { + if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } + + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper + .checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS); + final List dataValidationErrors = new ArrayList(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(ClientApiConstants.CLIENT_RESOURCE_NAME); + final JsonElement element = fromApiJsonHelper.parse(json); + + validateNote(baseDataValidator, element); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void validateNote(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + final String note = fromApiJsonHelper.extractStringNamed(TransferApiConstants.note, element); + baseDataValidator.reset().parameter(TransferApiConstants.note).value(note).notExceedingLengthOf(1000); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.java similarity index 88% rename from mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java rename to mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.java index 998ad1eb6..43908c27d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/exception/ClientNotAwaitingTransferApprovalException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalException.java @@ -1,4 +1,4 @@ -package org.mifosplatform.portfolio.client.exception; +package org.mifosplatform.portfolio.transfer.exception; import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java new file mode 100644 index 000000000..0527e5993 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/ClientNotAwaitingTransferApprovalOrOnHoldException.java @@ -0,0 +1,12 @@ +package org.mifosplatform.portfolio.transfer.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class ClientNotAwaitingTransferApprovalOrOnHoldException extends AbstractPlatformDomainRuleException { + + public ClientNotAwaitingTransferApprovalOrOnHoldException(final Long clientId) { + super("error.msg.client.not.awaiting.transfer.approval.or.on.hold.exception", "The Client with id `" + clientId + + "` is neither awaiting a transfer nor on hold", clientId); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java index a4959234d..4f55a68d6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/exception/TransferNotSupportedException.java @@ -13,39 +13,39 @@ import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRul */ 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"); + /*** enum of reasons for invalid Journal Entry **/ + public static enum TRANSFER_NOT_SUPPORTED_REASON { + SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME, ACTIVE_SAVINGS_ACCOUNT, BULK_CLIENT_TRANSFER_ACROSS_BRANCHES, DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH, DESTINATION_GROUP_HAS_NO_MEETING; + + public String errorMessage() { + if (name().toString().equalsIgnoreCase("ACTIVE_SAVINGS_ACCOUNT")) { + return "Cannot transfer Clients/Groups having active Savings accounts"; + } else if (name().toString().equalsIgnoreCase("BULK_CLIENT_TRANSFER_ACROSS_BRANCHES")) { + return "Bulk Transfers of clients between Groups in different branches not allowed "; + } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH")) { + return "Cannot transfer Clients with active accounts between groups with different meeting frequency"; + } else if (name().toString().equalsIgnoreCase("SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME")) { + return "Source and destination groups for bulk client transfers should be different"; + } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_HAS_NO_MEETING")) { return "Cannot transfer Client with active accounts to a groups with no meeting frequency"; } + return name().toString(); + } + + public String errorCode() { + if (name().toString().equalsIgnoreCase("ACTIVE_SAVINGS_ACCOUNT")) { + return "error.msg.entity.transfers.with.active.savings.accounts"; + } else if (name().toString().equalsIgnoreCase("BULK_CLIENT_TRANSFER_ACROSS_BRANCHES")) { + return "error.msg.groups.bulk.client.transfers.to.different.office"; + } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH")) { + return "error.msg.client.transfers.with.active.accounts.between.groups.with.different.meeting.frequency"; + } else if (name().toString().equalsIgnoreCase("SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME")) { + return "error.msg.groups.bulk.client.transfers.to.same.group"; + } else if (name().toString().equalsIgnoreCase("DESTINATION_GROUP_HAS_NO_MEETING")) { return "error.msg.client.transfers.with.active.accounts.to.group.with.no.meeting.frequencys"; } + return name().toString(); + } } - /** - * 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); + public TransferNotSupportedException(final TRANSFER_NOT_SUPPORTED_REASON reason, final Object... defaultUserMessageArgs) { + super(reason.errorCode(), reason.errorMessage(), defaultUserMessageArgs); } - /** - * 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); - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java index 3ec065a4f..ec2f2430f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/transfer/service/TransferWritePlatformServiceJpaRepositoryImpl.java @@ -6,6 +6,7 @@ package org.mifosplatform.portfolio.transfer.service; import java.util.ArrayList; +import java.util.Date; import java.util.List; import org.mifosplatform.infrastructure.core.api.JsonCommand; @@ -15,6 +16,7 @@ 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.organisation.staff.domain.StaffRepositoryWrapper; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; @@ -31,13 +33,19 @@ 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.note.service.NoteWritePlatformService; +import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository; import org.mifosplatform.portfolio.transfer.api.TransferApiConstants; import org.mifosplatform.portfolio.transfer.data.TransfersDataValidator; +import org.mifosplatform.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalException; +import org.mifosplatform.portfolio.transfer.exception.ClientNotAwaitingTransferApprovalOrOnHoldException; import org.mifosplatform.portfolio.transfer.exception.TransferNotSupportedException; +import org.mifosplatform.portfolio.transfer.exception.TransferNotSupportedException.TRANSFER_NOT_SUPPORTED_REASON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.google.common.collect.Iterables; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -50,13 +58,18 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr private final GroupRepositoryWrapper groupRepository; private final LoanWritePlatformService loanWritePlatformService; private final LoanRepository loanRepository; + private final SavingsAccountRepository savingsAccountRepository; private final TransfersDataValidator transfersDataValidator; + private final NoteWritePlatformService noteWritePlatformService; + private final StaffRepositoryWrapper staffRepositoryWrapper; @Autowired public TransferWritePlatformServiceJpaRepositoryImpl(final ClientRepositoryWrapper clientRepository, final OfficeRepositoryWrapper officeRepository, final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService, final GroupRepositoryWrapper groupRepository, - final LoanRepository loanRepository, final TransfersDataValidator transfersDataValidator) { + final LoanRepository loanRepository, final TransfersDataValidator transfersDataValidator, + final NoteWritePlatformService noteWritePlatformService, final StaffRepositoryWrapper staffRepositoryWrapper, + final SavingsAccountRepository savingsAccountRepository) { this.clientRepository = clientRepository; this.officeRepository = officeRepository; this.calendarInstanceRepository = calendarInstanceRepository; @@ -64,6 +77,9 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr this.groupRepository = groupRepository; this.loanRepository = loanRepository; this.transfersDataValidator = transfersDataValidator; + this.noteWritePlatformService = noteWritePlatformService; + this.staffRepositoryWrapper = staffRepositoryWrapper; + this.savingsAccountRepository = savingsAccountRepository; } @Override @@ -74,11 +90,26 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr final Group sourceGroup = this.groupRepository.findOneWithNotFoundDetection(sourceGroupId); final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName); final Group destinationGroup = this.groupRepository.findOneWithNotFoundDetection(destinationGroupId); + final Long staffId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.newStaffIdParamName); + final Boolean inheritDestinationGroupLoanOfficer = jsonCommand + .booleanObjectValueOfParameterNamed(TransferApiConstants.inheritDestinationGroupLoanOfficer); + Staff staff = null; + final Office sourceOffice = sourceGroup.getOffice(); + if (staffId != null) { + staff = this.staffRepositoryWrapper.findByOfficeHierarchyWithNotFoundDetection(staffId, sourceOffice.getHierarchy()); + } final List clients = this.assembleListOfClients(jsonCommand); + if (sourceGroupId == destinationGroupId) { throw new TransferNotSupportedException( + TRANSFER_NOT_SUPPORTED_REASON.SOURCE_AND_DESTINATION_GROUP_CANNOT_BE_SAME, sourceGroupId, destinationGroupId); } + + /*** Do not allow bulk client transfers across branches ***/ + if (!(sourceOffice.getId() == destinationGroup.getOffice().getId())) { throw new TransferNotSupportedException( + TRANSFER_NOT_SUPPORTED_REASON.BULK_CLIENT_TRANSFER_ACROSS_BRANCHES, sourceGroupId, destinationGroupId); } + for (Client client : clients) { - transferClientBetweenGroups(sourceGroup, client, destinationGroup); + transferClientBetweenGroups(sourceGroup, client, destinationGroup, inheritDestinationGroupLoanOfficer, staff); } return new CommandProcessingResultBuilder() // @@ -89,80 +120,64 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr /**** * Variables that would make sense
*
    - *
  • inheritDestinationGroupLoanOfficer: Default False
  • + *
  • inheritDestinationGroupLoanOfficer: Default true
  • *
  • newStaffId: Optional field with Id of new Loan Officer to be linked * to this client and all his JLG loans for this group
  • - *
  • 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
  • - *
  • - *
  • - *
  • - *
  • - *
  • - *
  • - *
  • - *
* ***/ @Transactional - public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup) { + public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup, + final Boolean inheritDestinationGroupLoanOfficer, final Staff newLoanOfficer) { // 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
- **/ - final CalendarInstance sourceGroupCalendarInstance = this.calendarInstanceRepository - .findByEntityIdAndEntityTypeIdAndCalendarTypeId(sourceGroup.getId(), CalendarEntityType.GROUPS.getValue(), + /** + * TODO: for now we need to ensure that only one collection sheet + * calendar can be linked with a center or group entity
+ **/ + 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 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()); - // get all customer loans synced with this group calendar Instance - final List 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( + TRANSFER_NOT_SUPPORTED_REASON.DESTINATION_GROUP_HAS_NO_MEETING, destinationGroup.getId()); - 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); } + Calendar sourceGroupCalendar = sourceGroupCalendarInstance.getCalendar(); + Calendar destinationGroupCalendar = destinationGroupCalendarInstance.getCalendar(); - } else { - Office destinationOffice = destinationGroup.getOffice(); - // handleClientTransfer(client, destinationOffice); - // TODO: Fill in the blanks + /*** + * 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( + TRANSFER_NOT_SUPPORTED_REASON.DESTINATION_GROUP_MEETING_FREQUENCY_MISMATCH, sourceGroup.getId(), + destinationGroup.getId()); } + + /** map all JLG loans for this client to the destinationGroup **/ + for (CalendarInstance calendarInstance : activeLoanCalendarInstances) { + calendarInstance.updateCalendar(destinationGroupCalendar); + this.calendarInstanceRepository.save(calendarInstance); + } + // reschedule all JLG Loans to follow new Calendar + this.loanWritePlatformService.applyMeetingDateChanges(destinationGroupCalendar, activeLoanCalendarInstances); } /** @@ -184,8 +199,11 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr for (Loan loan : allClientJLGLoans) { if (loan.status().isActiveOrAwaitingApprovalOrDisbursal()) { loan.updateGroup(destinationGroup); - if (destinationGroupLoanOfficer != null) { + if (inheritDestinationGroupLoanOfficer != null && inheritDestinationGroupLoanOfficer == true + && destinationGroupLoanOfficer != null) { loan.reassignLoanOfficer(destinationGroupLoanOfficer, DateUtils.getLocalDateOfTenant()); + } else if (newLoanOfficer != null) { + loan.reassignLoanOfficer(newLoanOfficer, DateUtils.getLocalDateOfTenant()); } this.loanRepository.saveAndFlush(loan); } @@ -212,14 +230,14 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr @Override public CommandProcessingResult proposeClientTransfer(final Long clientId, final JsonCommand jsonCommand) { // validation - this.transfersDataValidator.validateForProposeClientTransferBetweenBranches(jsonCommand.json()); + this.transfersDataValidator.validateForProposeClientTransfer(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(); - handleClientTransfer(client, office, TransferEventType.PROPOSAL); + handleClientTransferLifecycleEvent(client, office, TransferEventType.PROPOSAL, jsonCommand); this.clientRepository.save(client); return new CommandProcessingResultBuilder() // @@ -243,10 +261,14 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr @Transactional @Override public CommandProcessingResult acceptClientTransfer(final Long clientId, final JsonCommand jsonCommand) { + // validation + this.transfersDataValidator.validateForAcceptClientTransfer(jsonCommand.json()); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); - /*** Remove all group associations for this client ***/ + validateClientAwaitingTransferAcceptance(client); + + /*** Remove all (old) group associations for this client ***/ client.getGroups().clear(); - handleClientTransfer(client, client.getOffice(), TransferEventType.ACCEPTANCE); + handleClientTransferLifecycleEvent(client, client.getTransferToOffice(), TransferEventType.ACCEPTANCE, jsonCommand); this.clientRepository.save(client); return new CommandProcessingResultBuilder() // @@ -258,10 +280,12 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr @Override @Transactional public CommandProcessingResult withdrawClientTransfer(Long clientId, JsonCommand jsonCommand) { + // validation + this.transfersDataValidator.validateForWithdrawClientTransfer(jsonCommand.json()); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); - /*** Remove all group associations for this client ***/ - client.getGroups().clear(); - handleClientTransfer(client, client.getOffice(), TransferEventType.WITHDRAWAL); + validateClientAwaitingTransferAcceptanceOnHold(client); + + handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.WITHDRAWAL, jsonCommand); this.clientRepository.save(client); return new CommandProcessingResultBuilder() // @@ -273,10 +297,10 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr @Override @Transactional public CommandProcessingResult rejectClientTransfer(Long clientId, JsonCommand jsonCommand) { + // validation + this.transfersDataValidator.validateForRejectClientTransfer(jsonCommand.json()); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); - /*** Remove all group associations for this client ***/ - client.getGroups().clear(); - handleClientTransfer(client, client.getOffice(), TransferEventType.REJECTION); + handleClientTransferLifecycleEvent(client, client.getOffice(), TransferEventType.REJECTION, jsonCommand); this.clientRepository.save(client); return new CommandProcessingResultBuilder() // @@ -285,20 +309,36 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr .build(); } - private void handleClientTransfer(final Client client, final Office destinationOffice, TransferEventType transferEventType) { - if (loanRepository.doesClientHaveActiveLoans(client.getId())) { + private void handleClientTransferLifecycleEvent(final Client client, final Office destinationOffice, + TransferEventType transferEventType, JsonCommand jsonCommand) { + Date todaysDate = DateUtils.getDateOfTenant(); + /** Get destination loan officer if exists **/ + Staff staff = null; + Group destinationGroup = null; + final Long staffId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.newStaffIdParamName); + final Long destinationGroupId = jsonCommand.longValueOfParameterNamed(TransferApiConstants.destinationGroupIdParamName); + if (staffId != null) { + staff = this.staffRepositoryWrapper.findByOfficeHierarchyWithNotFoundDetection(staffId, destinationOffice.getHierarchy()); + } + if (destinationGroupId != null) { + destinationGroup = this.groupRepository.findOneWithNotFoundDetection(destinationGroupId); + } + + /*** Handle Active Loans ***/ + if (loanRepository.doNonClosedLoanAccountsExistForClient(client.getId())) { // get each individual loan for the client for (Loan loan : loanRepository.findLoanByClientId(client.getId())) { if (!loan.isClosed()) { switch (transferEventType) { case ACCEPTANCE: - this.loanWritePlatformService.acceptLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + this.loanWritePlatformService.acceptLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant(), + destinationOffice, staff); break; case PROPOSAL: this.loanWritePlatformService.initiateLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); break; case REJECTION: - this.loanWritePlatformService.rejectLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); + // do nothing break; case WITHDRAWAL: this.loanWritePlatformService.withdrawLoanTransfer(loan.getId(), DateUtils.getLocalDateOfTenant()); @@ -307,21 +347,34 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr } } + /*** Handle Active Savings (Currently throw and exception) ***/ + if (savingsAccountRepository.doNonClosedSavingAccountsExistForClient(client.getId())) { throw new TransferNotSupportedException( + TRANSFER_NOT_SUPPORTED_REASON.ACTIVE_SAVINGS_ACCOUNT, client.getId()); } + switch (transferEventType) { case ACCEPTANCE: client.setStatus(ClientStatus.ACTIVE.getValue()); + client.updateTransferToOffice(null); + client.updateOffice(destinationOffice); + client.updateOfficeJoiningDate(todaysDate); + if (client.getGroups().size() == 1) { + transferClientBetweenGroups(Iterables.get(client.getGroups(), 0), client, destinationGroup, true, staff); + } break; case PROPOSAL: client.setStatus(ClientStatus.TRANSFER_IN_PROGRESS.getValue()); - client.updateOffice(destinationOffice); + client.updateTransferToOffice(destinationOffice); break; case REJECTION: client.setStatus(ClientStatus.TRANSFER_ON_HOLD.getValue()); + client.updateTransferToOffice(null); break; - // TODO update client with previous office case WITHDRAWAL: client.setStatus(ClientStatus.ACTIVE.getValue()); + client.updateTransferToOffice(null); } + + noteWritePlatformService.createAndPersistClientNote(client, jsonCommand); } private List assembleListOfClients(final JsonCommand command) { @@ -345,4 +398,20 @@ public class TransferWritePlatformServiceJpaRepositoryImpl implements TransferWr return clients; } + private void validateClientAwaitingTransferAcceptance(final Client client) { + if (!client.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(client.getId()); } + } + + private void validateGroupAwaitingTransferAcceptance(final Group group) { + if (!group.isTransferInProgress()) { throw new ClientNotAwaitingTransferApprovalException(group.getId()); } + } + + private void validateClientAwaitingTransferAcceptanceOnHold(final Client client) { + if (!client.isTransferInProgressOrOnHold()) { throw new ClientNotAwaitingTransferApprovalOrOnHoldException(client.getId()); } + } + + private void validateGroupAwaitingTransferAcceptanceOnHold(final Group group) { + if (!group.isTransferInProgressOrOnHold()) { throw new ClientNotAwaitingTransferApprovalException(group.getId()); } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/resources/META-INF/persistence.xml b/mifosng-provider/src/main/resources/META-INF/persistence.xml index 872716cd5..b88528f21 100644 --- a/mifosng-provider/src/main/resources/META-INF/persistence.xml +++ b/mifosng-provider/src/main/resources/META-INF/persistence.xml @@ -17,6 +17,7 @@ + \ No newline at end of file