Added min/max number of clients per group rule for activate/activating

group
This commit is contained in:
Nayan
2014-10-27 06:13:52 +05:30
parent b3424921d5
commit cf085c6a8d
5 changed files with 259 additions and 142 deletions
@@ -298,9 +298,12 @@ public final class Client extends AbstractPersistable<Long> {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
validateNameParts(dataValidationErrors);
validateActivationDate(dataValidationErrors);
validateClientsGroupRules(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
public boolean isAccountNumberRequiresAutoGeneration() {
return this.accountNumberRequiresAutoGeneration;
}
@@ -568,6 +571,18 @@ public final class Client extends AbstractPersistable<Long> {
}
}
}
/*
* To become a part of a group, group may have set of criteria to be met
* before client can become member of it. This method is placeholder for
* such validation.
*/
private void validateClientsGroupRules(final List<ApiParameterError> dataValidationErrors) {
for (Group group : this.groups) {
group.validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(dataValidationErrors);
}
}
private void deriveDisplayName() {
@@ -6,10 +6,8 @@
package org.mifosplatform.portfolio.group.domain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@@ -31,7 +29,6 @@ import javax.persistence.TemporalType;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.infrastructure.codes.domain.CodeValue;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
@@ -40,7 +37,6 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
import org.mifosplatform.infrastructure.core.service.DateUtils;
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.client.domain.Client;
import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants;
import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException;
@@ -51,8 +47,6 @@ import org.mifosplatform.portfolio.group.exception.InvalidGroupStateTransitionEx
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.common.collect.Sets;
@Entity
@Table(name = "m_group")
public final class Group extends AbstractPersistable<Long> {
@@ -102,7 +96,7 @@ public final class Group extends AbstractPersistable<Long> {
@ManyToMany
@JoinTable(name = "m_group_client", joinColumns = @JoinColumn(name = "group_id"), inverseJoinColumns = @JoinColumn(name = "client_id"))
private Set<Client> clientMembers;
private Set<Client> clientMembers = new HashSet<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "closure_reason_cv_id", nullable = true)
@@ -123,17 +117,19 @@ public final class Group extends AbstractPersistable<Long> {
@ManyToOne(optional = true)
@JoinColumn(name = "submittedon_userid", nullable = true)
private AppUser submittedBy;
public Group() {
// JPA default constructor for entity
protected Group() {
this.name = null;
this.externalId = null;
this.clientMembers = new HashSet<>();
}
}
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<Client> clientMembers, final Set<Group> groupMembers, final LocalDate submittedOnDate, final AppUser currentUser) {
// By default new group is created in PENDING status, unless explicitly status is set to active
GroupingTypeStatus status = GroupingTypeStatus.PENDING;
LocalDate groupActivationDate = null;
if (active) {
@@ -148,6 +144,9 @@ public final class Group extends AbstractPersistable<Long> {
private 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<Client> clientMembers,
final Set<Group> groupMembers, final LocalDate submittedOnDate, final AppUser currentUser) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
this.office = office;
this.staff = staff;
this.groupLevel = groupLevel;
@@ -157,12 +156,6 @@ public final class Group extends AbstractPersistable<Long> {
this.parent.addChild(this);
}
this.status = status.getValue();
if (activationDate != null) {
this.activationDate = activationDate.toDate();
this.activatedBy = currentUser;
}
if (StringUtils.isNotBlank(name)) {
this.name = name.trim();
} else {
@@ -173,9 +166,7 @@ public final class Group extends AbstractPersistable<Long> {
} else {
this.externalId = null;
}
if (clientMembers != null) {
this.clientMembers = clientMembers;
}
if (groupMembers != null) {
this.groupMembers.addAll(groupMembers);
}
@@ -183,72 +174,59 @@ public final class Group extends AbstractPersistable<Long> {
this.submittedOnDate = submittedOnDate.toDate();
this.submittedBy = currentUser;
validate();
associateClients(clientMembers);
/*
* Always keep status change at the bottom, as status change rule depends on the attribute's value
*/
setStatus(activationDate, currentUser, status, dataValidationErrors);
throwExceptionIfErrors(dataValidationErrors);
}
private void setStatus(final LocalDate activationDate, final AppUser loginUser, final GroupingTypeStatus status,
final List<ApiParameterError> dataValidationErrors) {
if (status.isActive()) {
activate(loginUser, activationDate, dataValidationErrors);
}
else{
this.status = status.getValue();
}
}
private void validate() {
private void activate(final AppUser currentUser, final LocalDate activationLocalDate, final List<ApiParameterError> dataValidationErrors) {
validateStatusNotEqualToActiveAndLogError(dataValidationErrors);
validateRequiredNumberOfClientPerGroupAngLogError(dataValidationErrors);
if (dataValidationErrors.isEmpty()) {
this.status = GroupingTypeStatus.ACTIVE.getValue();
setActivationDate(activationLocalDate.toDate(), currentUser, dataValidationErrors);
}
}
public void activate(final AppUser currentUser, final LocalDate activationLocalDate) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
activate(currentUser, activationLocalDate, dataValidationErrors);
throwExceptionIfErrors(dataValidationErrors);
}
private void setActivationDate(final Date activationDate, final AppUser loginUser, final List<ApiParameterError> dataValidationErrors){
if (activationDate != null) {
this.activationDate = activationDate;
this.activatedBy = loginUser;
}
validateActivationDate(dataValidationErrors);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
final String defaultUserMessage = "Submitted on date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.in.the.future",
defaultUserMessage, ClientApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Submitted on date cannot be after the activation date";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.after.activation.date",
defaultUserMessage, GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) {
final String defaultUserMessage = "Activation date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.activationDate.in.the.future",
defaultUserMessage, GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null) {
if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Activation date cannot be a date before the office opening date.";
final ApiParameterError error = ApiParameterError.parameterError(
"error.msg.group.activationDate.cannot.be.before.office.activation.date", defaultUserMessage,
GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
}
}
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,
"activationDate", activationLocalDate.toString(formatter));
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(dataValidationErrors);
}
this.activationDate = activationLocalDate.toDate();
this.status = GroupingTypeStatus.ACTIVE.getValue();
this.activatedBy = currentUser;
validate();
}
public boolean isActivatedAfter(final LocalDate submittedOn) {
@@ -260,7 +238,7 @@ public final class Group extends AbstractPersistable<Long> {
}
public boolean isActive() {
return GroupingTypeStatus.fromInt(this.status).isActive();
return this.status != null ? GroupingTypeStatus.fromInt(this.status).isActive(): false;
}
private boolean isDateInTheFuture(final LocalDate localDate) {
@@ -334,10 +312,47 @@ public final class Group extends AbstractPersistable<Long> {
return activationLocalDate;
}
public void addClientMember(final Client member) {
this.clientMembers.add(member);
public List<String> associateClients(final Set<Client> clientMembersSet) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final List<String> newlyAssociatedClients = associateClients(clientMembersSet, dataValidationErrors);
throwExceptionIfErrors(dataValidationErrors);
return newlyAssociatedClients;
}
public List<String> associateClients(final Set<Client> clientMembersSet, final List<ApiParameterError> dataValidationErrors) {
final List<String> differences = new ArrayList<>();
for (final Client client : clientMembersSet) {
if (hasClientAsMember(client)) { throw new ClientExistInGroupException(client.getId(), getId()); }
this.clientMembers.add(client);
differences.add(client.getId().toString());
}
/*
* Minimum and maximum number of client rule is applicable only for active group
*/
if (isActive()) {
validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(dataValidationErrors);
}
return differences;
}
public List<String> disassociateClients(final Set<Client> clientMembersSet) {
final List<String> differences = new ArrayList<>();
for (final Client client : clientMembersSet) {
if (hasClientAsMember(client)) {
this.clientMembers.remove(client);
differences.add(client.getId().toString());
} else {
throw new ClientNotInGroupException(client.getId(), getId());
}
}
return differences;
}
public boolean hasClientAsMember(final Client client) {
return this.clientMembers.contains(client);
}
@@ -382,54 +397,6 @@ public final class Group extends AbstractPersistable<Long> {
this.staff = null;
}
public List<String> updateClientMembersIfDifferent(final Set<Client> clientMembersSet) {
List<String> differences = new ArrayList<>();
if (!clientMembersSet.equals(this.clientMembers)) {
final Set<Client> diffClients = Sets.symmetricDifference(clientMembersSet, this.clientMembers);
final String[] diffClientsIds = getClientIds(diffClients);
if (diffClientsIds != null) {
differences = Arrays.asList(diffClientsIds);
}
this.clientMembers = clientMembersSet;
}
return differences;
}
public List<String> associateClients(final Set<Client> clientMembersSet) {
final List<String> differences = new ArrayList<>();
for (final Client client : clientMembersSet) {
if (hasClientAsMember(client)) { throw new ClientExistInGroupException(client.getId(), getId()); }
this.clientMembers.add(client);
differences.add(client.getId().toString());
}
return differences;
}
public List<String> disassociateClients(final Set<Client> clientMembersSet) {
final List<String> differences = new ArrayList<>();
for (final Client client : clientMembersSet) {
if (hasClientAsMember(client)) {
this.clientMembers.remove(client);
differences.add(client.getId().toString());
} else {
throw new ClientNotInGroupException(client.getId(), getId());
}
}
return differences;
}
private String[] getClientIds(final Set<Client> clients) {
final String[] clientIds = new String[clients.size()];
final Iterator<Client> it = clients.iterator();
for (int i = 0; it.hasNext(); i++) {
clientIds[i] = it.next().getId().toString();
}
return clientIds;
}
public GroupLevel getGroupLevel() {
return this.groupLevel;
}
@@ -567,4 +534,123 @@ public final class Group extends AbstractPersistable<Long> {
return differences;
}
public void validateRequiredNumberOfClientPerGroupAngLogError(final List<ApiParameterError> dataValidationErrors) {
if (!hasGroupRequiredNumberOfClients()) {
// This validation not strictly related to a parameter, that is the
// reason parameterName is set null
final String parameterName = null;
final String globalisationMessageCode = "error.msg.group.activation.without.required.number.of.members";
final String defaultUserMessage = "Group should have number of members between " + this.groupLevel.getMinClients() + " and "
+ this.groupLevel.getMaxClients();
final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName);
dataValidationErrors.add(error);
}
}
private Boolean hasGroupRequiredNumberOfClients() {
/*
* If maximum clients required per group is set to zero or less then zero
* then irrespective of minimum required number, below condition returns true
* that means this is rule is not validated
*/
if (this.clientMembers.size() >= this.groupLevel.getMinClients()
&& (this.clientMembers.size() < this.groupLevel.getMaxClients() || this.groupLevel.getMaxClients() <= 0)) { return true; }
return false;
}
public void validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(final List<ApiParameterError> dataValidationErrors) {
if (hasGroupMoreThanRequiredNumberOfClients()) {
// This validation not strictly related to a parameter, that is the
// reason parameterName is set null
final String parameterName = null;
final String globalisationMessageCode = "error.msg.group.can.not.have.more.than.required.number.of.members";
final String defaultUserMessage = "Group should have number of members between " + this.groupLevel.getMinClients() + " and "
+ this.groupLevel.getMaxClients();
final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, parameterName);
dataValidationErrors.add(error);
}
}
private Boolean hasGroupMoreThanRequiredNumberOfClients(){
/*
* If maximum clients required per group is set to zero or less then zero
* then below condition returns always false
* that means this is rule is not validated and group can have unlimited number of clients.
*/
if (this.clientMembers.size() > this.groupLevel.getMaxClients() && this.groupLevel.getMaxClients() > 0) { return true; }
return false;
}
private void validateActivationDate(final List<ApiParameterError> dataValidationErrors) {
if (getSubmittedOnDate() != null && isDateInTheFuture(getSubmittedOnDate())) {
final String defaultUserMessage = "Submitted on date cannot be in the future.";
final String globalisationMessageCode = "error.msg.group.submittedOnDate.in.the.future";
final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode ,
defaultUserMessage, GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && getSubmittedOnDate() != null && getSubmittedOnDate().isAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Submitted on date cannot be after the activation date";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.submittedOnDate.after.activation.date",
defaultUserMessage, GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate);
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null && isDateInTheFuture(getActivationLocalDate())) {
final String defaultUserMessage = "Activation date cannot be in the future.";
final ApiParameterError error = ApiParameterError.parameterError("error.msg.group.activationDate.in.the.future",
defaultUserMessage, GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
if (getActivationLocalDate() != null) {
if (this.office.isOpeningDateAfter(getActivationLocalDate())) {
final String defaultUserMessage = "Activation date cannot be a date before the office opening date.";
final ApiParameterError error = ApiParameterError.parameterError(
"error.msg.group.activationDate.cannot.be.before.office.activation.date", defaultUserMessage,
GroupingTypesApiConstants.activationDateParamName, getActivationLocalDate());
dataValidationErrors.add(error);
}
}
}
private void validateStatusNotEqualToActiveAndLogError(final List<ApiParameterError> dataValidationErrors){
if (isActive()) {
final String defaultUserMessage = "Cannot activate group. Group is already active.";
final String globalisationMessageCode = "error.msg.group.already.active";
final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage,
GroupingTypesApiConstants.activeParamName, true);
dataValidationErrors.add(error);
}
}
private void throwExceptionIfErrors(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -30,6 +30,12 @@ public class GroupLevel extends AbstractPersistable<Long> {
@Column(name = "can_have_clients", nullable = false)
private boolean canHaveClients = false;
@Column(name = "min_clients", nullable = false)
private final Long minClients;
@Column(name = "max_clients", nullable = false)
private final Long maxClients;
public GroupLevel() {
this.parentId = null;
@@ -37,17 +43,21 @@ public class GroupLevel extends AbstractPersistable<Long> {
this.levelName = null;
this.recursable = false;
this.canHaveClients = false;
this.minClients = null;
this.maxClients = null;
}
public GroupLevel(final Long parentId, final boolean isSuperParent, final String levelName, final boolean recursable,
final boolean canHaveClients) {
final boolean canHaveClients, final Long minClients, final Long maxClients) {
this.superParent = isSuperParent;
this.parentId = parentId;
this.levelName = levelName;
this.recursable = recursable;
this.canHaveClients = canHaveClients;
this.minClients = minClients;
this.maxClients = maxClients;
}
@@ -78,4 +88,12 @@ public class GroupLevel extends AbstractPersistable<Long> {
public boolean isCenter() {
return this.levelName.equalsIgnoreCase("Center");
}
public Long getMinClients(){
return this.minClients;
}
public Long getMaxClients(){
return this.maxClients;
}
}
@@ -10,13 +10,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandProcessingService;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
@@ -229,12 +226,10 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId);
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale);
final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate");
validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(group.getOffice(), group.getGroupLevel(), activationDate);
group.activate(currentUser, fmt, activationDate);
group.activate(currentUser, activationDate);
this.groupRepository.saveAndFlush(group);
@@ -0,0 +1,3 @@
ALTER TABLE `m_group_level`
ADD COLUMN `min_clients` INT(11) NOT NULL DEFAULT '0' AFTER `can_have_clients`,
ADD COLUMN `max_clients` INT(11) NOT NULL DEFAULT '0' AFTER `min_clients`;