cleaning up transfers functionality and prepping for 1.9 release

This commit is contained in:
Vishwas Babu A J
2013-09-04 08:21:54 +05:30
parent 6650946b1e
commit d93f630c9b
30 changed files with 610 additions and 264 deletions
+123 -23
View File
@@ -90,7 +90,7 @@
<body>
<div id="page-wrapper">
<div id="flybar">
<div id="nav-logo">1.8.1.RELEASE API</div>
<div id="nav-logo">1.9.0.RELEASE API</div>
<div class="flybar-nav">
<h2 class="flybar-button">Overview</h2>
<div class="flybar-menu-overview">
@@ -209,8 +209,32 @@
</tr>
<tr class="alt">
<td></td>
<td>clients/{clientId}?command=transfer</td>
<td><a href="#clients_transfer">Transfer Client</a></td>
<td>clients/{clientId}?command=proposeTransfer</td>
<td><a href="#clients_propose_transfer">Propose a Client Transfer</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>clients/{clientId}?command=withdrawTransfer</td>
<td><a href="#clients_withdraw_transfer">Withdraw Client Transfer Proposal</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="alt">
<td></td>
<td>clients/{clientId}?command=acceptTransfer</td>
<td><a href="#clients_accept_transfer">Accept Client Transfer</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>clients/{clientId}?command=rejectTransfer</td>
<td><a href="#clients_reject_transfer">Reject Client Transfer</a></td>
<td></td>
<td></td>
<td></td>
@@ -586,7 +610,7 @@
<tr>
<td></td>
<td>groups/{groupId}?command=transferClients</td>
<td><a href="#groups_transfer_clients">Transfer Clients</a></td>
<td><a href="#groups_transfer_clients">Bulk Transfer Clients across Groups</a></td>
<td></td>
<td></td>
<td></td>
@@ -2138,19 +2162,20 @@ Request Body:
</div>
</div>
<a id="clients_transfer" name="clients_transfer" class="old-syle-anchor">&nbsp;</a>
<a id="clients_propose_transfer" name="clients_propose_transfer" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Transfer a Client</h4>
<p>Allows you to transfer a <i>Client</i> between <i>Offices</i>.</p>
<h4>Propose a Client Transfer</h4>
<p>Allows you to propose the transfer of a <i>Client</i> to a different <i>Offices</i>.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=transfer</code>
<code class="method-request">POST clients/1?command=transfer
<code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=proposeTransfer</code>
<code class="method-request">POST clients/1?command=proposeTransfer
Content-Type: application/json
Request Body:
{
destinationOfficeId:1
"destinationOfficeId":"2",
"note":"Client Relocating to Bangalore"
}
</code>
<code class="method-response">
@@ -2162,7 +2187,92 @@ Request Body:
</code>
</div>
</div>
</div>
<a id="clients_withdraw_transfer" name="clients_withdraw_transfer" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Withdraw a Client Transfer</h4>
<p>Allows you to withdraw the proposed the transfer of a <i>Client</i> to a different <i>Offices</i>.</p>
<p>Withdrawal can happen only if the destination Branch (to which the transfer was
<a href="#clients_propose_transfer">proposed</a>) has not already
<a href="#clients_accept_transfer">accepted</a> the transfer proposal </p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=withdrawTransfer</code>
<code class="method-request">POST clients/1?command=withdrawTransfer
Content-Type: application/json
Request Body:
{
"note":"Sorry, data entry error"
}
</code>
<code class="method-response">
{
"clientId": 2,
"resourceId": 2
}
</code>
</div>
</div>
<a id="clients_reject_transfer" name="clients_reject_transfer" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Reject a Client Transfer</h4>
<p>Allows the Destination Branch to reject the <a href="#clients_propose_transfer">proposed</a> Client Transfer.</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=rejectTransfer</code>
<code class="method-request">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"
}
</code>
<code class="method-response">
{
"clientId": 2,
"resourceId": 2
}
</code>
</div>
</div>
<a id="clients_accept_transfer" name="clients_accept_transfer" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Reject a Client Transfer</h4>
<p>Allows the Destination Branch to accept the <a href="#clients_propose_transfer">proposed</a> Client Transfer.</p>
<p>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</p>
</div>
<div class="method-example">
<code class="method-declaration">POST https://Domain Name/api/v1/clients/{clientId}?command=acceptTransfer</code>
<code class="method-request">POST clients/1?command=acceptTransfer
Content-Type: application/json
Request Body:
{
"note":"Due Diligence done and all documents received"
}
</code>
<code class="method-response">
{
"clientId": 2,
"resourceId": 2
}
</code>
</div>
</div>
<a id="clients_retrieve" name="clients_retrieve" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
@@ -3866,7 +3976,8 @@ Request Body:
<td>destinationGroupId</td>
</tr>
<tr>
<td class=fielddesc>The identifier of the group to which the clients are to be transferred.</td>
<td class=fielddesc>The identifier of the group to which the clients are to be transferred (
has to be in the same branch as the source Group).</td>
</tr>
<tr class=alt>
<td>clients</td>
@@ -3883,15 +3994,6 @@ Request Body:
should be linked to the assigned loan officer for the group
</td>
</tr>
<tr class=alt>
<td>transferActiveLoans</td>
</tr>
<tr>
<td class=fielddesc>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
</td>
</tr>
</table>
<br/>
<table class=matrixHeading>
@@ -3911,8 +4013,6 @@ Request Body:
<td>inheritDestinationGroupLoanOfficer (defaults to true), transferActiveLoans (defaults to true)</td>
</tr>
</table>
</div>
@@ -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;
@@ -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;
@@ -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;
@@ -27,7 +27,7 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
/**Should use in clause, can I do it without creating a new class?**/
@Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id=:groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3")
@Query("from CalendarInstance ci where ci.entityId in (select id from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId and (loan.loanStatus = 100 or loan.loanStatus = 200 or loan.loanStatus = 300)) and ci.entityTypeId = 3")
List<CalendarInstance> findCalendarInstancesForActiveLoansByGroupIdAndClientId(@Param("clientId") Long clientId,
@Param("groupId") Long groupId);
}
@@ -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<String> CLIENT_UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<String>(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<String> CLIENT_RESPONSE_DATA_PARAMETERS = new HashSet<String>(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<String> ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, activationDateParamName));
public static final Set<String> CLIENT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, closureDateParamName, closureReasonIdParamName));
}
@@ -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);
}
@@ -40,6 +40,8 @@ final public class ClientData implements Comparable<ClientData> {
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<ClientData> {
public static ClientData template(final Long officeId, final LocalDate joinedDate, final Collection<OfficeData> officeOptions,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> 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<GroupGeneralData> 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<OfficeData> allowedOffices, final Collection<GroupGeneralData> groups,
final Collection<StaffData> staffOptions, final Collection<CodeValueData> closureReasons) {
this.accountNo = accountNo;
@@ -111,6 +116,8 @@ final public class ClientData implements Comparable<ClientData> {
}
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);
@@ -55,6 +55,10 @@ public final class Client extends AbstractPersistable<Long> {
@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<Long> {
@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<Long> {
}
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<Long> {
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<Long> {
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<Long> {
}
this.activationDate = activationLocalDate.toDate();
this.officeJoiningDate = activationDate;
this.status = ClientStatus.ACTIVE.getValue();
validate();
@@ -260,6 +274,14 @@ public final class Client extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
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) {
@@ -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<ClientData> {
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);
}
}
@@ -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<GroupGeneralData> groups;
if (officeId != null && groupId != null) {
groups = this.groupReadPlatformService.retrieveGroupsForLookup(officeId, groupId);
} else {
groups = new ArrayList<GroupGeneralData>();
}
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<GroupGeneralData> groups = this.groupReadPlatformService.retrieveAll(searchParameters);
@@ -186,7 +174,7 @@ public class GroupsApiResource {
GroupRoleData selectedRole = null;
Collection<CalendarData> 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<Integer> calendarTypeOptions = CalendarUtils.createIntegerListFromQueryParameter("all");
calendars = this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), calendarTypeOptions);
final List<Integer> 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 })
@@ -387,6 +387,14 @@ public final class Group extends AbstractPersistable<Long> {
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()) {
@@ -18,5 +18,5 @@ public interface GroupReadPlatformService {
GroupGeneralData retrieveOne(Long groupId);
Collection<GroupGeneralData> retrieveGroupsForLookup(Long officeId, Long groupId);
Collection<GroupGeneralData> retrieveGroupsForLookup(Long officeId);
}
@@ -206,17 +206,17 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService {
}
@Override
public Collection<GroupGeneralData> retrieveGroupsForLookup(final Long officeId, final Long groupId) {
public Collection<GroupGeneralData> 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<GroupGeneralData> {
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
@@ -52,7 +52,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
public static final String FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP = "Select loan.loanProduct.id from Loan loan where "
+ "loan.group.id = :groupId and loan.loanStatus = :loanStatus and loan.client.id is NULL group by loan.loanProduct.id";
public static final String DOES_CLIENT_HAVE_ACTIVE_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300,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<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
@@ -117,7 +117,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_ACTIVE_LOANS_PRODUCT_IDS_BY_GROUP)
List<Long> findActiveLoansLoanProductIdsByGroup(@Param("groupId") Long groupId, @Param("loanStatus") Integer loanStatus);
@Query(DOES_CLIENT_HAVE_ACTIVE_LOANS)
boolean doesClientHaveActiveLoans(@Param("clientId") Long clientId);
@Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS)
boolean doNonClosedLoanAccountsExistForClient(@Param("clientId") Long clientId);
}
@@ -137,6 +137,13 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
.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);
@@ -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;
@@ -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);
}
@@ -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<Long> existingTransactionIds = new ArrayList<Long>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(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<Long> existingTransactionIds = new ArrayList<Long>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<Long>(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()); }
}
}
}
@@ -76,7 +76,7 @@ public class Note extends AbstractAuditableCustom<AppUser, Long> {
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();
@@ -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);
}
@@ -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();
@@ -16,11 +16,13 @@ public interface SavingsAccountRepository extends JpaRepository<SavingsAccount,
@Query("from SavingsAccount s_acc where s_acc.client.id = :clientId")
List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId);
@Query("from SavingsAccount s_acc where s_acc.status = :status")
List<SavingsAccount> findSavingAccountByStatus(@Param("status") Integer status);
@Query("from SavingsAccount sa where sa.client.id = :clientId and sa.group.id = :groupId")
List<SavingsAccount> 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);
}
@@ -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<String> TRANSFER_CLIENTS_BETWEEN_GROUPS_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, destinationGroupIdParamName, clients, inheritDestinationGroupLoanOfficer, newStaffIdParamName,
transferActiveLoans));
public static final Set<String> PROPOSE_CLIENT_TRANSFER_BETWEEN_BRANCHES_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(
localeParamName, dateFormatParamName, destinationOfficeIdParamName, newStaffIdParamName, transferActiveLoans));
public static final Set<String> PROPOSE_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, destinationOfficeIdParamName, transferActiveLoans, note));
public static final Set<String> ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(newStaffIdParamName,
destinationGroupIdParamName, note));
public static final Set<String> REJECT_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(note));
public static final Set<String> WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(note));
}
@@ -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<Map<String, Object>>() {}.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<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
@@ -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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.ACCEPT_CLIENT_TRANSFER_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.REJECT_CLIENT_TRANSFER_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper
.checkForUnsupportedParameters(typeOfMap, json, TransferApiConstants.WITHDRAW_CLIENT_TRANSFER_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
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);
}
}
@@ -1,4 +1,4 @@
package org.mifosplatform.portfolio.client.exception;
package org.mifosplatform.portfolio.transfer.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
@@ -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);
}
}
@@ -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);
}
}
@@ -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<Client> 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 <br/>
* <ul>
* <li>inheritDestinationGroupLoanOfficer: Default False</li>
* <li>inheritDestinationGroupLoanOfficer: Default true</li>
* <li>newStaffId: Optional field with Id of new Loan Officer to be linked
* to this client and all his JLG loans for this group</li>
* <li>transferActiveLoans:Default False, unless overridden, this client
* cannot be transferred between groups within the same branch if he has
* active JLG loans and across branches if he has any active loan</li>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* ***/
@Transactional
public void transferClientBetweenGroups(final Group sourceGroup, final Client client, final Group destinationGroup) {
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 <br/>
**/
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 <br/>
**/
final CalendarInstance sourceGroupCalendarInstance = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(sourceGroup.getId(), CalendarEntityType.GROUPS.getValue(),
CalendarType.COLLECTION.getValue());
// get all customer loans synced with this group calendar Instance
final List<CalendarInstance> activeLoanCalendarInstances = this.calendarInstanceRepository
.findCalendarInstancesForActiveLoansByGroupIdAndClientId(sourceGroup.getId(), client.getId());
/**
* if a calendar is present in the source group along with loans synced
* with it, we should ensure that the destination group also has a
* collection calendar
**/
if (sourceGroupCalendarInstance != null && !activeLoanCalendarInstances.isEmpty()) {
// get the destination calendar
final CalendarInstance destinationGroupCalendarInstance = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(destinationGroup.getId(), CalendarEntityType.GROUPS.getValue(),
CalendarType.COLLECTION.getValue());
// get all customer loans synced with this group calendar Instance
final List<CalendarInstance> activeLoanCalendarInstances = this.calendarInstanceRepository
.findCalendarInstancesForActiveLoansByGroupIdAndClientId(sourceGroup.getId(), client.getId());
/**
* if a calendar is present in the source group along with loans
* synced with it, we should ensure that the destination group also
* has a collection calendar
**/
if (sourceGroupCalendarInstance != null && !activeLoanCalendarInstances.isEmpty()) {
// get the destination calendar
final CalendarInstance destinationGroupCalendarInstance = this.calendarInstanceRepository
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(destinationGroup.getId(), CalendarEntityType.GROUPS.getValue(),
CalendarType.COLLECTION.getValue());
if (destinationGroupCalendarInstance == null) { throw new TransferNotSupportedException(
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<Client> 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()); }
}
}
@@ -17,6 +17,7 @@
<!-- <property name="hibernate.hbm2ddl.auto" value="update"/> -->
<property name="hibernate.jdbc.batch_size" value="100" />
<property name="hibernate.order_inserts" value="true" />
<!-- <property name = "hibernate.show_sql" value = "true" /> -->
</properties>
</persistence-unit>
</persistence>