diff --git a/mifosng-db/migrations/core_db/V129__client_and_group_timeline.sql b/mifosng-db/migrations/core_db/V129__client_and_group_timeline.sql new file mode 100644 index 000000000..1c88248bf --- /dev/null +++ b/mifosng-db/migrations/core_db/V129__client_and_group_timeline.sql @@ -0,0 +1,9 @@ +ALTER TABLE `m_client` ADD `submittedon_date` DATE NULL DEFAULT NULL , +ADD `submittedon_userid` BIGINT( 20 ) NULL DEFAULT NULL , +ADD `activatedon_userid` BIGINT( 20 ) NULL DEFAULT NULL , +ADD `closedon_userid` BIGINT( 20 ) NULL DEFAULT NULL ; + +ALTER TABLE `m_group` ADD `activatedon_userid` BIGINT( 20 ) NULL , +ADD `submittedon_date` DATE NULL , +ADD `submittedon_userid` BIGINT( 20 ) NULL , +ADD `closedon_userid` BIGINT( 20 ) NULL ; \ No newline at end of file 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 dc30c9bf7..077a480ce 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 @@ -37,6 +37,7 @@ public class ClientApiConstants { public static final String staffIdParamName = "staffId"; public static final String closureDateParamName = "closureDate"; public static final String closureReasonIdParamName = "closureReasonId"; + public static final String submittedOnDateParamName="submittedOnDate"; // response parameters public static final String statusParamName = "status"; public static final String hierarchyParamName = "hierarchy"; 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 c4e23f962..670197d14 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 @@ -135,6 +135,7 @@ public class ClientsApiResource { clientData = ClientData.templateOnTop(clientData, templateData); } + return this.toApiJsonSerializer.serialize(settings, clientData, ClientApiConstants.CLIENT_RESPONSE_DATA_PARAMETERS); } 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 d9ec36283..b7a64e82c 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 @@ -48,6 +48,7 @@ final public class ClientData implements Comparable { private final Boolean imagePresent; private final Long staffId; private final String staffName; + private final ClientTimelineData timeline; // associations private final Collection groups; @@ -61,7 +62,8 @@ 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, null, null, null, joinedDate, null, - null, null, officeOptions, null, staffOptions, closureReasons); + null, null, officeOptions, null, staffOptions, closureReasons,null); + } public static ClientData templateOnTop(final ClientData clientData, final ClientData templateData) { @@ -70,7 +72,8 @@ final public class ClientData implements Comparable { clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename, clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo, clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, templateData.officeOptions, - clientData.groups, templateData.staffOptions, null); + clientData.groups, templateData.staffOptions, null,null); + } public static ClientData setParentGroups(final ClientData clientData, final Collection parentGroups) { @@ -78,7 +81,8 @@ final public class ClientData implements Comparable { clientData.transferToOfficeId, clientData.transferToOfficeName, clientData.id, clientData.firstname, clientData.middlename, clientData.lastname, clientData.fullname, clientData.displayName, clientData.externalId, clientData.mobileNo, clientData.activationDate, clientData.imageId, clientData.staffId, clientData.staffName, clientData.officeOptions, - parentGroups, clientData.staffOptions, null); + parentGroups, clientData.staffOptions, null,clientData.timeline); + } public static ClientData clientIdentifier(final Long id, final String accountNo, final EnumOptionData status, final String firstname, @@ -86,20 +90,23 @@ final public class ClientData implements Comparable { final String officeName) { return new ClientData(accountNo, status, officeId, officeName, null, null, id, firstname, middlename, lastname, fullname, - displayName, null, null, null, null, null, null, null, null, null, null); + displayName, null, null, 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, null, null, id, null, null, null, null, displayName, null, null, null, - null, null, null, null, null, null, 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 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 String mobileNo, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName) { + final String mobileNo, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName,final ClientTimelineData timeline) { return new ClientData(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, middlename, - lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName, null, null, null, null); + lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName, null, null, null, null,timeline); + } private ClientData(final String accountNo, final EnumOptionData status, final Long officeId, final String officeName, @@ -107,7 +114,7 @@ final public class ClientData implements Comparable { final String middlename, final String lastname, final String fullname, final String displayName, final String externalId, final String mobileNo, final LocalDate activationDate, final Long imageId, final Long staffId, final String staffName, final Collection allowedOffices, final Collection groups, - final Collection staffOptions, final Collection closureReasons) { + final Collection staffOptions, final Collection closureReasons,final ClientTimelineData timeline) { this.accountNo = accountNo; this.status = status; if (status != null) { @@ -144,6 +151,8 @@ final public class ClientData implements Comparable { this.officeOptions = allowedOffices; this.staffOptions = staffOptions; this.closureReasons = closureReasons; + + this.timeline = timeline; } public Long id() { @@ -170,6 +179,10 @@ final public class ClientData implements Comparable { return this.imagePresent; } + public ClientTimelineData getTimeline() { + return this.timeline; + } + @Override public int compareTo(final ClientData obj) { if (obj == null) { return -1; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientTimelineData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientTimelineData.java new file mode 100644 index 000000000..badc54527 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/data/ClientTimelineData.java @@ -0,0 +1,60 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.client.data; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData; + +import java.math.BigDecimal; + +/** + * Immutable data object represent the important time-line events of a loan + * application and loan. + */ +@SuppressWarnings("unused") +public class ClientTimelineData { + + private final LocalDate submittedOnDate; + private final String submittedByUsername; + private final String submittedByFirstname; + private final String submittedByLastname; + + private final LocalDate activatedOnDate; + private final String activatedByUsername; + private final String activatedByFirstname; + private final String activatedByLastname; + + private final LocalDate closedOnDate; + private final String closedByUsername; + private final String closedByFirstname; + private final String closedByLastname; + + + public ClientTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, + final String submittedByFirstname, final String submittedByLastname, final LocalDate activatedOnDate, + final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname, + final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname, + final String closedByLastname) { + this.submittedOnDate = submittedOnDate; + this.submittedByUsername = submittedByUsername; + this.submittedByFirstname = submittedByFirstname; + this.submittedByLastname = submittedByLastname; + + this.activatedOnDate = activatedOnDate; + this.activatedByUsername = activatedByUsername; + this.activatedByFirstname = activatedByFirstname; + this.activatedByLastname = activatedByLastname; + + this.closedOnDate = closedOnDate; + this.closedByUsername = closedByUsername; + this.closedByFirstname = closedByFirstname; + this.closedByLastname = closedByLastname; + + } + + +} \ No newline at end of file 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 116605480..2df294665 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 @@ -27,6 +27,7 @@ import javax.persistence.TemporalType; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; +import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; @@ -44,6 +45,7 @@ import org.mifosplatform.organisation.office.domain.Office; import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.portfolio.client.api.ClientApiConstants; import org.mifosplatform.portfolio.group.domain.Group; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -121,7 +123,27 @@ public final class Client extends AbstractPersistable { @Temporal(TemporalType.DATE) private Date closureDate; - public static Client createNew(final Office clientOffice, final Group clientParentGroup, final Staff staff, final JsonCommand command) { + @ManyToOne(optional = true) + @JoinColumn(name = "closedon_userid", nullable = true) + private AppUser closeddBy; + + @Column(name = "submittedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date submittedOnDate; + + @ManyToOne(optional = true) + @JoinColumn(name = "submittedon_userid", nullable = true) + private AppUser submittedBy; + + @ManyToOne(optional = true) + @JoinColumn(name = "activatedon_userid", nullable = true) + private AppUser activatedBy; + + + + + + public static Client createNew(final AppUser currentUser, final Office clientOffice, final Group clientParentGroup, final Staff staff, final JsonCommand command) { final String accountNo = command.stringValueOfParameterNamed(ClientApiConstants.accountNoParamName); final String externalId = command.stringValueOfParameterNamed(ClientApiConstants.externalIdParamName); @@ -132,12 +154,23 @@ public final class Client extends AbstractPersistable { final String lastname = command.stringValueOfParameterNamed(ClientApiConstants.lastnameParamName); final String fullname = command.stringValueOfParameterNamed(ClientApiConstants.fullnameParamName); + + LocalDate submittedOnDate = new LocalDate(); + + if (command.hasParameter(ClientApiConstants.submittedOnDateParamName)) { + + submittedOnDate = command.localDateValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName); + + } + ClientStatus status = ClientStatus.PENDING; boolean active = false; if (command.hasParameter("active")) { active = command.booleanPrimitiveValueOfParameterNamed(ClientApiConstants.activeParamName); } + + LocalDate activationDate = null; LocalDate officeJoiningDate = null; if (active) { @@ -146,23 +179,48 @@ public final class Client extends AbstractPersistable { officeJoiningDate = activationDate; } - return new Client(status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname, activationDate, - officeJoiningDate, externalId, mobileNo, staff); + + return new Client(currentUser,status, clientOffice, clientParentGroup, accountNo, firstname, middlename, lastname, fullname, activationDate, + officeJoiningDate, externalId, mobileNo, staff,submittedOnDate); + + } protected Client() { // } - private Client(final ClientStatus status, final Office office, final Group clientParentGroup, final String accountNo, + private Client(final AppUser currentUser,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 LocalDate officeJoiningDate, final String externalId, final String mobileNo, final Staff staff) { + final LocalDate officeJoiningDate, final String externalId, final String mobileNo, final Staff staff,final LocalDate submittedOnDate) { + if (StringUtils.isBlank(accountNo)) { this.accountNumber = new RandomPasswordGenerator(19).generate(); this.accountNumberRequiresAutoGeneration = true; } else { this.accountNumber = accountNo; } + + this.submittedBy = currentUser; + + if (isDateInTheFuture(submittedOnDate)) { + + final String defaultUserMessage = "submitted date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.submittedOnDate.in.the.future", + defaultUserMessage, ClientApiConstants.submittedOnDateParamName,submittedOnDate); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + this.submittedOnDate = submittedOnDate.toDate(); + + + + + this.status = status.getValue(); this.office = office; if (StringUtils.isNotBlank(externalId)) { @@ -246,7 +304,7 @@ public final class Client extends AbstractPersistable { this.accountNumberRequiresAutoGeneration = false; } - public void activate(final DateTimeFormatter formatter, final LocalDate activationLocalDate) { + public void activate(final AppUser currentUser,final DateTimeFormatter formatter, final LocalDate activationLocalDate) { if (isActive()) { final String defaultUserMessage = "Cannot activate client. Client is already active."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.clients.already.active", defaultUserMessage, @@ -271,6 +329,7 @@ public final class Client extends AbstractPersistable { } this.activationDate = activationLocalDate.toDate(); + this.activatedBy = currentUser; this.officeJoiningDate = this.activationDate; this.status = ClientStatus.ACTIVE.getValue(); @@ -449,6 +508,10 @@ public final class Client extends AbstractPersistable { this.displayName = nameBuilder.toString(); } + public LocalDate getSubmittedOnDate() { + return (LocalDate) ObjectUtils.defaultIfNull(new LocalDate(this.submittedOnDate), null); + } + public LocalDate getActivationLocalDate() { LocalDate activationLocalDate = null; if (this.activationDate != null) { @@ -545,9 +608,10 @@ public final class Client extends AbstractPersistable { return this.groups; } - public void close(final CodeValue closureReason, final Date closureDate) { + public void close(final AppUser currentUser, final CodeValue closureReason, final Date closureDate) { this.closureReason = closureReason; this.closureDate = closureDate; + this.closeddBy = currentUser; this.status = ClientStatus.CLOSED.getValue(); } 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 0cebe021f..17ea307ab 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 @@ -27,6 +27,7 @@ import org.mifosplatform.organisation.office.service.OfficeReadPlatformService; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.service.StaffReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; +import org.mifosplatform.portfolio.client.data.ClientTimelineData; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.domain.ClientStatus; import org.mifosplatform.portfolio.client.exception.ClientNotFoundException; @@ -263,13 +264,32 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService sqlBuilder.append("c.fullname as fullname, c.display_name as displayName, "); sqlBuilder.append("c.mobile_no as mobileNo, "); sqlBuilder.append("c.activation_date as activationDate, c.image_id as imageId, "); - sqlBuilder.append("c.staff_id as staffId, s.display_name as staffName "); + sqlBuilder.append("c.staff_id as staffId, s.display_name as staffName,"); + + sqlBuilder.append("c.submittedon_date as submittedOnDate, "); + sqlBuilder.append("sbu.username as submittedByUsername, "); + sqlBuilder.append("sbu.firstname as submittedByFirstname, "); + sqlBuilder.append("sbu.lastname as submittedByLastname, "); + + sqlBuilder.append("c.closedon_date as closedOnDate, "); + sqlBuilder.append("clu.username as closedByUsername, "); + sqlBuilder.append("clu.firstname as closedByFirstname, "); + sqlBuilder.append("clu.lastname as closedByLastname, "); + + sqlBuilder.append("acu.username as activatedByUsername, "); + sqlBuilder.append("acu.firstname as activatedByFirstname, "); + sqlBuilder.append("acu.lastname as activatedByLastname "); + sqlBuilder.append("from m_client c "); 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 "); + sqlBuilder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid "); + sqlBuilder.append("left join m_appuser acu on acu.id = c.activatedon_userid "); + sqlBuilder.append("left join m_appuser clu on clu.id = c.closedon_userid "); + this.schema = sqlBuilder.toString(); } @@ -305,8 +325,28 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final Long staffId = JdbcSupport.getLong(rs, "staffId"); final String staffName = rs.getString("staffName"); + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final ClientTimelineData timeline = new ClientTimelineData (submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname, + activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, + closedByFirstname, closedByLastname); + return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, - middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName); + middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName,timeline); + } } @@ -338,12 +378,34 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService 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.mobile_no as mobileNo, "); + + + builder.append("c.submittedon_date as submittedOnDate, "); + builder.append("sbu.username as submittedByUsername, "); + builder.append("sbu.firstname as submittedByFirstname, "); + builder.append("sbu.lastname as submittedByLastname, "); + + builder.append("c.closedon_date as closedOnDate, "); + builder.append("clu.username as closedByUsername, "); + builder.append("clu.firstname as closedByFirstname, "); + builder.append("clu.lastname as closedByLastname, "); + + // builder.append("c.submittedon as submittedOnDate, "); + builder.append("acu.username as activatedByUsername, "); + builder.append("acu.firstname as activatedByFirstname, "); + builder.append("acu.lastname as activatedByLastname, "); + + builder.append("c.activation_date as activationDate, c.image_id as imageId, "); builder.append("c.staff_id as staffId, s.display_name as staffName "); 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 "); + builder.append("left join m_appuser sbu on sbu.id = c.submittedon_userid "); + builder.append("left join m_appuser acu on acu.id = c.activatedon_userid "); + builder.append("left join m_appuser clu on clu.id = c.closedon_userid "); + this.schema = builder.toString(); } @@ -378,8 +440,27 @@ public class ClientReadPlatformServiceImpl implements ClientReadPlatformService final Long staffId = JdbcSupport.getLong(rs, "staffId"); final String staffName = rs.getString("staffName"); + final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate"); + final String closedByUsername = rs.getString("closedByUsername"); + final String closedByFirstname = rs.getString("closedByFirstname"); + final String closedByLastname = rs.getString("closedByLastname"); + + final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate"); + final String submittedByUsername = rs.getString("submittedByUsername"); + final String submittedByFirstname = rs.getString("submittedByFirstname"); + final String submittedByLastname = rs.getString("submittedByLastname"); + + final String activatedByUsername = rs.getString("activatedByUsername"); + final String activatedByFirstname = rs.getString("activatedByFirstname"); + final String activatedByLastname = rs.getString("activatedByLastname"); + + final ClientTimelineData timeline = new ClientTimelineData (submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname, + activationDate, activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, + closedByFirstname, closedByLastname); + return ClientData.instance(accountNo, status, officeId, officeName, transferToOfficeId, transferToOfficeName, id, firstname, - middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName); + middlename, lastname, fullname, displayName, externalId, mobileNo, activationDate, imageId, staffId, staffName,timeline); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java index 4110617e0..8be65be77 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/client/service/ClientWritePlatformServiceJpaRepositoryImpl.java @@ -44,6 +44,7 @@ import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.savings.domain.SavingsAccount; import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository; +import org.mifosplatform.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -136,6 +137,9 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP public CommandProcessingResult createClient(final JsonCommand command) { try { + + final AppUser currentUser = this.context.authenticatedUser(); + this.fromApiJsonDeserializer.validateForCreate(command.json()); final Long officeId = command.longValueOfParameterNamed(ClientApiConstants.officeIdParamName); @@ -157,7 +161,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP staff = this.staffRepository.findByOfficeHierarchyWithNotFoundDetection(staffId, clientOffice.getHierarchy()); } - final Client newClient = Client.createNew(clientOffice, clientParentGroup, staff, command); + final Client newClient = Client.createNew(currentUser,clientOffice, clientParentGroup, staff, command); this.clientRepository.save(newClient); @@ -235,7 +239,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); - client.activate(fmt, activationDate); + final AppUser currentUser = this.context.authenticatedUser(); + client.activate(currentUser, fmt, activationDate); this.clientRepository.saveAndFlush(client); @@ -328,6 +333,8 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP @Override public CommandProcessingResult closeClient(final Long clientId, final JsonCommand command) { try { + + AppUser currentUser = this.context.authenticatedUser(); this.fromApiJsonDeserializer.validateClose(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); @@ -385,7 +392,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP } } - client.close(closureReason, closureDate.toDate()); + client.close(currentUser,closureReason, closureDate.toDate()); this.clientRepository.saveAndFlush(client); return new CommandProcessingResultBuilder() // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java index ced073ce3..4c7d4a54e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/api/GroupingTypesApiConstants.java @@ -42,6 +42,8 @@ public class GroupingTypesApiConstants { public static final String activationDateParamName = "activationDate"; public static final String groupMembersParamName = "groupMembers"; + public static final String submittedOnDateParamName = "submittedOnDate"; + // group parameters public static final String centerIdParamName = "centerId"; public static final String clientMembersParamName = "clientMembers"; @@ -55,6 +57,7 @@ public class GroupingTypesApiConstants { public static final String staffOptionsParamName = "staffOptions"; public static final String clientOptionsParamName = "clientOptions"; public static final String collectionMeetingCalendar = "collectionMeetingCalendar"; + public static final String timeLine ="timeline"; // group close parameters public static final String closureDateParamName = "closureDate"; @@ -66,7 +69,7 @@ public class GroupingTypesApiConstants { public static final Set GROUP_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, idParamName, nameParamName, externalIdParamName, centerIdParamName, officeIdParamName, staffIdParamName, activeParamName, - activationDateParamName, clientMembersParamName, collectionMeetingCalendar)); + activationDateParamName, clientMembersParamName, collectionMeetingCalendar,submittedOnDateParamName)); public static final Set GROUP_ROLES_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(roleParamName, clientIdParamName)); @@ -86,7 +89,7 @@ public class GroupingTypesApiConstants { public static final Set GROUP_RESPONSE_DATA_PARAMETERS = new HashSet(Arrays.asList(idParamName, nameParamName, externalIdParamName, officeIdParamName, officeNameParamName, "parentId", "parentName", staffIdParamName, staffNameParamName, - hierarchyParamName, officeOptionsParamName, staffOptionsParamName, clientOptionsParamName)); + hierarchyParamName, officeOptionsParamName, staffOptionsParamName, clientOptionsParamName,timeLine)); public static final Set ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet(Arrays.asList(localeParamName, dateFormatParamName, activationDateParamName)); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java index f5236e2ae..d0ef63e60 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupGeneralData.java @@ -52,13 +52,14 @@ public class GroupGeneralData { private final Collection availableRoles; private final GroupRoleData selectedRole; private final Collection closureReasons; + private final GroupTimelineData timeline; public static GroupGeneralData lookup(final Long groupId, final String groupName) { final Collection clientMembers = null; final Collection groupRoles = null; final Collection closureReasons = null; return new GroupGeneralData(groupId, groupName, null, null, null, null, null, null, null, null, null, null, clientMembers, null, - null, null, null, groupRoles, null, null, null, null, closureReasons); + null, null, null, groupRoles, null, null, null, null, closureReasons,null); } public static GroupGeneralData template(final Long officeId, final Long centerId, final String centerName, final Long staffId, @@ -72,7 +73,7 @@ public class GroupGeneralData { return new GroupGeneralData(null, null, null, null, null, officeId, null, centerId, centerName, staffId, staffName, null, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, null, null, null, - closureReasons); + closureReasons,null); } public static GroupGeneralData withTemplate(final GroupGeneralData templatedGrouping, final GroupGeneralData grouping) { @@ -80,7 +81,7 @@ public class GroupGeneralData { grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, grouping.clientMembers, templatedGrouping.centerOptions, templatedGrouping.officeOptions, templatedGrouping.staffOptions, templatedGrouping.clientOptions, grouping.groupRoles, templatedGrouping.availableRoles, - grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons); + grouping.selectedRole, grouping.calendarsData, grouping.collectionMeetingCalendar, grouping.closureReasons,templatedGrouping.timeline); } public static GroupGeneralData withAssocations(final GroupGeneralData grouping, final Collection membersOfGroup, @@ -90,12 +91,12 @@ public class GroupGeneralData { grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, membersOfGroup, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions, grouping.clientOptions, groupRoles, grouping.availableRoles, grouping.selectedRole, calendarsData, - collectionMeetingCalendar, grouping.closureReasons); + collectionMeetingCalendar, grouping.closureReasons,grouping.timeline); } public static GroupGeneralData instance(final Long id, final String name, final String externalId, final EnumOptionData status, final LocalDate activationDate, final Long officeId, final String officeName, final Long centerId, final String centerName, - final Long staffId, final String staffName, final String hierarchy) { + final Long staffId, final String staffName, final String hierarchy,final GroupTimelineData timeline) { final Collection clientMembers = null; final Collection centerOptions = null; @@ -111,7 +112,7 @@ public class GroupGeneralData { return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId, staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, - role, calendarsData, collectionMeetingCalendar, closureReasons); + role, calendarsData, collectionMeetingCalendar, closureReasons,timeline); } private GroupGeneralData(final Long id, final String name, final String externalId, final EnumOptionData status, @@ -121,7 +122,7 @@ public class GroupGeneralData { final Collection staffOptions, final Collection clientOptions, final Collection groupRoles, final Collection availableRoles, final GroupRoleData role, final Collection calendarsData, final CalendarData collectionMeetingCalendar, - final Collection closureReasons) { + final Collection closureReasons,final GroupTimelineData timeline) { this.id = id; this.name = name; this.externalId = externalId; @@ -159,6 +160,7 @@ public class GroupGeneralData { this.calendarsData = calendarsData; this.collectionMeetingCalendar = collectionMeetingCalendar; this.closureReasons = closureReasons; + this.timeline = timeline; } public Long getId() { @@ -190,7 +192,7 @@ public class GroupGeneralData { grouping.officeId, grouping.officeName, grouping.centerId, grouping.centerName, grouping.staffId, grouping.staffName, grouping.hierarchy, grouping.clientMembers, grouping.centerOptions, grouping.officeOptions, grouping.staffOptions, grouping.clientOptions, grouping.groupRoles, grouping.availableRoles, selectedRole, grouping.calendarsData, - grouping.collectionMeetingCalendar, grouping.closureReasons); + grouping.collectionMeetingCalendar, grouping.closureReasons,null); } public static GroupGeneralData withClosureReasons(final Collection closureReasons) { @@ -219,6 +221,6 @@ public class GroupGeneralData { return new GroupGeneralData(id, name, externalId, status, activationDate, officeId, officeName, centerId, centerName, staffId, staffName, hierarchy, clientMembers, centerOptions, officeOptions, staffOptions, clientOptions, groupRoles, availableRoles, - role, calendarsData, collectionMeetingCalendar, closureReasons); + role, calendarsData, collectionMeetingCalendar, closureReasons,null); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTimelineData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTimelineData.java new file mode 100644 index 000000000..fa3426586 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/data/GroupTimelineData.java @@ -0,0 +1,56 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.group.data; + +import org.joda.time.LocalDate; + +/** + * Immutable data object represent the important time-line events of a loan + * application and loan. + */ +@SuppressWarnings("unused") +public class GroupTimelineData { + + private final LocalDate submittedOnDate; + private final String submittedByUsername; + private final String submittedByFirstname; + private final String submittedByLastname; + + private final LocalDate activatedOnDate; + private final String activatedByUsername; + private final String activatedByFirstname; + private final String activatedByLastname; + + private final LocalDate closedOnDate; + private final String closedByUsername; + private final String closedByFirstname; + private final String closedByLastname; + + + public GroupTimelineData(final LocalDate submittedOnDate, final String submittedByUsername, + final String submittedByFirstname, final String submittedByLastname, final LocalDate activatedOnDate, + final String activatedByUsername, final String activatedByFirstname, final String activatedByLastname, + final LocalDate closedOnDate, final String closedByUsername, final String closedByFirstname, + final String closedByLastname) { + this.submittedOnDate = submittedOnDate; + this.submittedByUsername = submittedByUsername; + this.submittedByFirstname = submittedByFirstname; + this.submittedByLastname = submittedByLastname; + + this.activatedOnDate = activatedOnDate; + this.activatedByUsername = activatedByUsername; + this.activatedByFirstname = activatedByFirstname; + this.activatedByLastname = activatedByLastname; + + this.closedOnDate = closedOnDate; + this.closedByUsername = closedByUsername; + this.closedByFirstname = closedByFirstname; + this.closedByLastname = closedByLastname; + + } + + +} \ No newline at end of file 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 235e94ea3..4f4abf301 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 @@ -43,6 +43,7 @@ import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants; import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException; import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException; import org.mifosplatform.portfolio.group.exception.InvalidGroupStateTransitionException; +import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; import com.google.common.collect.Sets; @@ -64,6 +65,10 @@ public final class Group extends AbstractPersistable { @Temporal(TemporalType.DATE) private Date activationDate; + @ManyToOne(optional = true) + @JoinColumn(name = "activatedon_userid", nullable = true) + private AppUser activatedBy; + @ManyToOne @JoinColumn(name = "office_id", nullable = false) private Office office; @@ -106,6 +111,18 @@ public final class Group extends AbstractPersistable { @Temporal(TemporalType.DATE) private Date closureDate; + @ManyToOne(optional = true) + @JoinColumn(name = "closedon_userid", nullable = true) + private AppUser closedBy; + + @Column(name = "submittedon_date", nullable = true) + @Temporal(TemporalType.DATE) + private Date submittedOnDate; + + @ManyToOne(optional = true) + @JoinColumn(name = "submittedon_userid", nullable = true) + private AppUser submittedBy; + public Group() { this.name = null; this.externalId = null; @@ -114,7 +131,7 @@ public final class Group extends AbstractPersistable { public static Group newGroup(final Office office, final Staff staff, final Group parent, final GroupLevel groupLevel, final String name, final String externalId, final boolean active, final LocalDate activationDate, - final Set clientMembers, final Set groupMembers) { + final Set clientMembers, final Set groupMembers,final LocalDate submittedOnDate,final AppUser currentUser) { GroupingTypeStatus status = GroupingTypeStatus.PENDING; LocalDate groupActivationDate = null; @@ -124,12 +141,14 @@ public final class Group extends AbstractPersistable { // group is made active } - return new Group(office, staff, parent, groupLevel, name, externalId, status, groupActivationDate, clientMembers, groupMembers); + + + return new Group(office, staff, parent, groupLevel, name, externalId, status, groupActivationDate, clientMembers, groupMembers,submittedOnDate,currentUser); } public Group(final Office office, final Staff staff, final Group parent, final GroupLevel groupLevel, final String name, final String externalId, final GroupingTypeStatus status, final LocalDate activationDate, final Set clientMembers, - final Set groupMembers) { + final Set groupMembers,final LocalDate submittedOnDate,final AppUser currentUser) { this.office = office; this.staff = staff; this.groupLevel = groupLevel; @@ -160,9 +179,25 @@ public final class Group extends AbstractPersistable { if (groupMembers != null) { this.groupMembers.addAll(groupMembers); } + + this.submittedBy = currentUser; + + if (isDateInTheFuture(submittedOnDate)) { + + final String defaultUserMessage = "submitted date cannot be in the future."; + final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.in.the.future", + defaultUserMessage,"SubmittedOnDate"); + + final List dataValidationErrors = new ArrayList(); + dataValidationErrors.add(error); + + throw new PlatformApiDataValidationException(dataValidationErrors); + } + + this.submittedOnDate = submittedOnDate.toDate(); } - public void activate(final DateTimeFormatter formatter, final LocalDate activationLocalDate) { + public void activate(final AppUser currentUser,final DateTimeFormatter formatter, final LocalDate activationLocalDate) { if (isActive()) { final String defaultUserMessage = "Cannot activate group. Group is already active."; final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.already.active", defaultUserMessage, @@ -188,6 +223,7 @@ public final class Group extends AbstractPersistable { this.activationDate = activationLocalDate.toDate(); this.status = GroupingTypeStatus.ACTIVE.getValue(); + this.activatedBy = currentUser; } public boolean isActivatedAfter(final LocalDate submittedOn) { @@ -424,7 +460,7 @@ public final class Group extends AbstractPersistable { return GroupingTypeStatus.fromInt(this.status).isClosed(); } - public void close(final CodeValue closureReason, final LocalDate closureDate) { + public void close(final AppUser currentUser,final CodeValue closureReason, final LocalDate closureDate) { if (isClosed()) { final String errorMessage = "Group with identifier " + getId() + " is alread closed."; @@ -441,6 +477,7 @@ public final class Group extends AbstractPersistable { this.closureReason = closureReason; this.closureDate = closureDate.toDate(); this.status = GroupingTypeStatus.CLOSED.getValue(); + this.closedBy = currentUser; } public boolean hasActiveClients() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/AllGroupTypesDataMapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/AllGroupTypesDataMapper.java index 936adb586..93c7983ea 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/AllGroupTypesDataMapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/AllGroupTypesDataMapper.java @@ -13,6 +13,7 @@ import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.domain.JdbcSupport; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.group.data.GroupGeneralData; +import org.mifosplatform.portfolio.group.data.GroupTimelineData; import org.springframework.jdbc.core.RowMapper; /** @@ -29,11 +30,30 @@ public final class AllGroupTypesDataMapper implements RowMapper groupMembers = assembleSetOfChildGroups(officeId, command); + LocalDate submittedOnDate = new LocalDate(); + + if(command.hasParameter(GroupingTypesApiConstants.submittedOnDateParamName)){ + + submittedOnDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.submittedOnDateParamName); + } + final boolean active = command.booleanPrimitiveValueOfParameterNamed(GroupingTypesApiConstants.activeParamName); final Group newGroup = Group.newGroup(groupOffice, staff, parentGroup, groupLevel, name, externalId, active, activationDate, - clientMembers, groupMembers); + clientMembers, groupMembers,submittedOnDate,currentUser); // pre-save to generate id for use in group hierarchy this.groupRepository.save(newGroup); @@ -184,6 +193,8 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group try { this.fromApiJsonDeserializer.validateForActivation(command, GroupingTypesApiConstants.GROUP_RESOURCE_NAME); + AppUser currentUser = this.context.authenticatedUser(); + final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId); final Locale locale = command.extractLocale(); @@ -191,7 +202,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(group.getOffice(), group.getGroupLevel(), activationDate); - group.activate(fmt, activationDate); + group.activate(currentUser,fmt, activationDate); this.groupRepository.saveAndFlush(group); @@ -416,6 +427,8 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final LocalDate closureDate = command.localDateValueOfParameterNamed(GroupingTypesApiConstants.closureDateParamName); final Long closureReasonId = command.longValueOfParameterNamed(GroupingTypesApiConstants.closureReasonIdParamName); + final AppUser currentUser = this.context.authenticatedUser(); + final CodeValue closureReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection( GroupingTypesApiConstants.GROUP_CLOSURE_REASON, closureReasonId); @@ -428,7 +441,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group validateLoansAndSavingsForGroupOrCenterClose(group, closureDate); - group.close(closureReason, closureDate); + group.close(currentUser,closureReason, closureDate); this.groupRepository.saveAndFlush(group); @@ -489,6 +502,8 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final CodeValue closureReason = this.codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection( GroupingTypesApiConstants.GROUP_CLOSURE_REASON, closureReasonId); + final AppUser currentUser = this.context.authenticatedUser(); + if (center.hasActiveGroups()) { final String errorMessage = center.getGroupLevel().getLevelName() + " cannot be closed because of active groups associated with it."; @@ -498,7 +513,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group validateLoansAndSavingsForGroupOrCenterClose(center, closureDate); - center.close(closureReason, closureDate); + center.close(currentUser,closureReason, closureDate); this.groupRepository.saveAndFlush(center); 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 2efe26092..1c76395bc 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 @@ -672,7 +672,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final EnumOptionData groupStatus = ClientEnumerations.status(groupStatusEnum); final LocalDate activationDate = JdbcSupport.getLocalDate(rs, "activationDate"); groupData = GroupGeneralData.instance(groupId, groupName, groupExternalId, groupStatus, activationDate, groupOfficeId, - null, groupParentId, null, groupStaffId, null, groupHierarchy); + null, groupParentId, null, groupStaffId, null, groupHierarchy,null); } final Integer loanCounter = JdbcSupport.getInteger(rs, "loanCounter");