From 702ecd480f373384873058e20cd65bd527491319 Mon Sep 17 00:00:00 2001 From: Vishwas Babu A J Date: Mon, 3 Nov 2014 21:09:02 +0530 Subject: [PATCH] cleaning up group validations --- .../domain/ConfigurationDomainService.java | 10 +- .../domain/ConfigurationDomainServiceJpa.java | 28 +++- .../portfolio/client/domain/Client.java | 24 +-- ...WritePlatformServiceJpaRepositoryImpl.java | 28 +++- .../portfolio/group/domain/Group.java | 152 +++++++----------- .../portfolio/group/domain/GroupLevel.java | 24 +-- ...erCountNotInPermissibleRangeException.java | 17 ++ ...WritePlatformServiceJpaRepositoryImpl.java | 33 +++- .../V208__min_max_clients_in_group_redux.sql | 9 ++ 9 files changed, 179 insertions(+), 146 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java create mode 100644 mifosng-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java index 182b3cbf6..e7146a302 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -34,9 +34,13 @@ public interface ConfigurationDomainService { Long retrievePasswordLiveTime(); Long retrieveGraceOnPenaltyPostingPeriod(); - + boolean isSavingsInterestPostingAtCurrentPeriodEnd(); - + Integer retrieveFinancialYearBeginningMonth(); - + + public Integer retrieveMinAllowedClientsInGroup(); + + public Integer retrieveMaxAllowedClientsInGroup(); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 3e48b0d73..3b025b6dd 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -113,7 +113,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); return property.getValue(); } - + @Override public Long retrieveGraceOnPenaltyPostingPeriod() { final String propertyName = "grace-on-penalty-posting"; @@ -121,7 +121,6 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService return property.getValue(); } - @Override public boolean isPasswordForcedResetEnable() { final String propertyName = "force-password-reset-days"; @@ -135,21 +134,36 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); return property.getValue(); } - + @Override public boolean isSavingsInterestPostingAtCurrentPeriodEnd() { final String propertyName = "savings-interest-posting-current-period-end"; final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); return property.isEnabled(); } - + @Override public Integer retrieveFinancialYearBeginningMonth() { final String propertyName = "financial-year-beginning-month"; final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); - if(property.isEnabled()) - return property.getValue().intValue(); - return 1; + if (property.isEnabled()) return property.getValue().intValue(); + return 1; + } + + @Override + public Integer retrieveMinAllowedClientsInGroup() { + final String propertyName = "min-clients-in-group"; + final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); + if (property.isEnabled()) { return property.getValue().intValue(); } + return null; + } + + @Override + public Integer retrieveMaxAllowedClientsInGroup() { + final String propertyName = "max-clients-in-group"; + final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); + if (property.isEnabled()) { return property.getValue().intValue(); } + return null; } } \ 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 abb9d364a..0dd779fa2 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 @@ -298,12 +298,11 @@ public final class Client extends AbstractPersistable { final List dataValidationErrors = new ArrayList<>(); validateNameParts(dataValidationErrors); validateActivationDate(dataValidationErrors); - validateClientsGroupRules(dataValidationErrors); - + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } - + } - + public boolean isAccountNumberRequiresAutoGeneration() { return this.accountNumberRequiresAutoGeneration; } @@ -449,7 +448,6 @@ public final class Client extends AbstractPersistable { actualChanges.put(ClientApiConstants.savingsProductIdParamName, newValue); } - if (command.isChangeInLongParameterNamed(ClientApiConstants.genderIdParamName, genderId())) { final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName); actualChanges.put(ClientApiConstants.genderIdParamName, newValue); @@ -465,7 +463,6 @@ public final class Client extends AbstractPersistable { actualChanges.put(ClientApiConstants.clientClassificationIdParamName, newValue); } - final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); @@ -490,7 +487,7 @@ public final class Client extends AbstractPersistable { this.dateOfBirth = newValue.toDate(); } - if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.submittedOnDateParamName,getSubmittedOnDate())) { + if (command.isChangeInLocalDateParameterNamed(ClientApiConstants.submittedOnDateParamName, getSubmittedOnDate())) { final String valueAsInput = command.stringValueOfParameterNamed(ClientApiConstants.submittedOnDateParamName); actualChanges.put(ClientApiConstants.submittedOnDateParamName, valueAsInput); actualChanges.put(ClientApiConstants.dateFormatParamName, dateFormatAsInput); @@ -507,7 +504,6 @@ public final class Client extends AbstractPersistable { return actualChanges; } - private void validateNameParts(final List dataValidationErrors) { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("client"); @@ -571,18 +567,6 @@ public final class Client extends AbstractPersistable { } } } - - /* - * 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 dataValidationErrors) { - for (Group group : this.groups) { - group.validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(dataValidationErrors); - } - } private void deriveDisplayName() { 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 50487b651..674405321 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 @@ -18,6 +18,7 @@ import org.mifosplatform.commands.service.CommandProcessingService; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -42,6 +43,7 @@ import org.mifosplatform.portfolio.client.exception.InvalidClientSavingProductEx import org.mifosplatform.portfolio.client.exception.InvalidClientStateTransitionException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepository; +import org.mifosplatform.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException; import org.mifosplatform.portfolio.group.exception.GroupNotFoundException; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; @@ -82,6 +84,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP private final SavingsProductRepository savingsProductRepository; private final SavingsApplicationProcessWritePlatformService savingsApplicationProcessWritePlatformService; private final CommandProcessingService commandProcessingService; + private final ConfigurationDomainService configurationDomainService; @Autowired public ClientWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -91,7 +94,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP final CodeValueRepositoryWrapper codeValueRepository, final LoanRepository loanRepository, final SavingsAccountRepository savingsRepository, final SavingsProductRepository savingsProductRepository, final SavingsApplicationProcessWritePlatformService savingsApplicationProcessWritePlatformService, - final CommandProcessingService commandProcessingService) { + final CommandProcessingService commandProcessingService, final ConfigurationDomainService configurationDomainService) { this.context = context; this.clientRepository = clientRepository; this.officeRepository = officeRepository; @@ -106,6 +109,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.savingsProductRepository = savingsProductRepository; this.savingsApplicationProcessWritePlatformService = savingsApplicationProcessWritePlatformService; this.commandProcessingService = commandProcessingService; + this.configurationDomainService = configurationDomainService; } @Transactional @@ -215,6 +219,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP clientType, clientClassification, command); boolean rollbackTransaction = false; if (newClient.isActive()) { + validateParentGroupRulesBeforeClientActivation(newClient); final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateClient(null).build(); rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); } @@ -275,7 +280,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP } clientForUpdate.updateStaff(newStaff); } - + if (changes.containsKey(ClientApiConstants.genderIdParamName)) { final Long newValue = command.longValueOfParameterNamed(ClientApiConstants.genderIdParamName); @@ -351,6 +356,7 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP this.fromApiJsonDeserializer.validateActivation(command); final Client client = this.clientRepository.findOneWithNotFoundDetection(clientId); + validateParentGroupRulesBeforeClientActivation(client); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -568,4 +574,22 @@ public class ClientWritePlatformServiceJpaRepositoryImpl implements ClientWriteP .build(); } + /* + * To become a part of a group, group may have set of criteria to be met + * before client can become member of it. + */ + private void validateParentGroupRulesBeforeClientActivation(Client client) { + Integer minNumberOfClients = configurationDomainService.retrieveMinAllowedClientsInGroup(); + Integer maxNumberOfClients = configurationDomainService.retrieveMaxAllowedClientsInGroup(); + for (Group group : client.getGroups()) { + /** + * Since this Client has not yet been associated with the group, + * reduce maxNumberOfClients by 1 + **/ + final boolean validationsuccess = group.isGroupsClientCountWithinMaxRange(maxNumberOfClients - 1); + if (!validationsuccess) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minNumberOfClients, + maxNumberOfClients); } + } + } + } \ 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 c92a7dafb..9498fd62c 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 @@ -117,19 +117,20 @@ public final class Group extends AbstractPersistable { @ManyToOne(optional = true) @JoinColumn(name = "submittedon_userid", nullable = true) private AppUser submittedBy; - - // JPA default constructor for entity + + // 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 clientMembers, final Set groupMembers, final LocalDate submittedOnDate, final AppUser currentUser) { - - // By default new group is created in PENDING status, unless explicitly status is set to active + + // 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) { @@ -144,7 +145,7 @@ public final class Group extends AbstractPersistable { 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 clientMembers, final Set groupMembers, final LocalDate submittedOnDate, final AppUser currentUser) { - + final List dataValidationErrors = new ArrayList<>(); this.office = office; @@ -175,24 +176,23 @@ public final class Group extends AbstractPersistable { this.submittedBy = currentUser; associateClients(clientMembers); - + /* - * Always keep status change at the bottom, as status change rule depends on the attribute's value + * 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 dataValidationErrors) { if (status.isActive()) { activate(loginUser, activationDate, dataValidationErrors); - } - else{ + } else { this.status = status.getValue(); } @@ -201,13 +201,11 @@ public final class Group extends AbstractPersistable { private void activate(final AppUser currentUser, final LocalDate activationLocalDate, final List 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) { @@ -215,10 +213,10 @@ public final class Group extends AbstractPersistable { final List dataValidationErrors = new ArrayList<>(); activate(currentUser, activationLocalDate, dataValidationErrors); throwExceptionIfErrors(dataValidationErrors); - + } - private void setActivationDate(final Date activationDate, final AppUser loginUser, final List dataValidationErrors){ + private void setActivationDate(final Date activationDate, final AppUser loginUser, final List dataValidationErrors) { if (activationDate != null) { this.activationDate = activationDate; @@ -226,7 +224,7 @@ public final class Group extends AbstractPersistable { } validateActivationDate(dataValidationErrors); - + } public boolean isActivatedAfter(final LocalDate submittedOn) { @@ -238,7 +236,7 @@ public final class Group extends AbstractPersistable { } public boolean isActive() { - return this.status != null ? GroupingTypeStatus.fromInt(this.status).isActive(): false; + return this.status != null ? GroupingTypeStatus.fromInt(this.status).isActive() : false; } private boolean isDateInTheFuture(final LocalDate localDate) { @@ -313,15 +311,6 @@ public final class Group extends AbstractPersistable { } public List associateClients(final Set clientMembersSet) { - - final List dataValidationErrors = new ArrayList<>(); - final List newlyAssociatedClients = associateClients(clientMembersSet, dataValidationErrors); - throwExceptionIfErrors(dataValidationErrors); - return newlyAssociatedClients; - - } - - public List associateClients(final Set clientMembersSet, final List dataValidationErrors) { final List differences = new ArrayList<>(); for (final Client client : clientMembersSet) { if (hasClientAsMember(client)) { throw new ClientExistInGroupException(client.getId(), getId()); } @@ -329,13 +318,6 @@ public final class Group extends AbstractPersistable { differences.add(client.getId().toString()); } - /* - * Minimum and maximum number of client rule is applicable only for active group - */ - if (isActive()) { - validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(dataValidationErrors); - } - return differences; } @@ -352,7 +334,7 @@ public final class Group extends AbstractPersistable { return differences; } - + public boolean hasClientAsMember(final Client client) { return this.clientMembers.contains(client); } @@ -425,6 +407,10 @@ public final class Group extends AbstractPersistable { return this.groupLevel.isCenter(); } + public boolean isGroup() { + return this.groupLevel.isGroup(); + } + public boolean isTransferInProgress() { return GroupingTypeStatus.fromInt(this.status).isTransferInProgress(); } @@ -512,7 +498,6 @@ public final class Group extends AbstractPersistable { .getParent().getId(), group.getId()); } - this.groupMembers.add(group); differences.add(group.getId().toString()); } @@ -535,76 +520,55 @@ public final class Group extends AbstractPersistable { return differences; } - public void validateRequiredNumberOfClientPerGroupAngLogError(final List dataValidationErrors) { + public Boolean isGroupsClientCountWithinMinMaxRange(Integer minClients, Integer maxClients) { - if (!hasGroupRequiredNumberOfClients()) { + if (maxClients == null && minClients == null) { return true; } - // 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); + // set minClients or maxClients to 0 if null + if (minClients == null) { + minClients = 0; } - } - - 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; } + if (maxClients == null) { + maxClients = Integer.MAX_VALUE; + } + + Set activeClientMembers = getActiveClientMembers(); + + if (activeClientMembers.size() >= minClients && activeClientMembers.size() <= maxClients) { return true; } return false; - } - public void validateGroupHasMoreThanRequiredNumberOfClientsAngLogError(final List 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); - + public Boolean isGroupsClientCountWithinMaxRange(Integer maxClients) { + Set activeClientMembers = getActiveClientMembers(); + if (maxClients == null) { + return true; + } else if (activeClientMembers.size() <= maxClients) { + return true; + } else { + return false; } } - 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; - + public Set getActiveClientMembers() { + Set activeClientMembers = new HashSet<>(); + for (Client client : this.clientMembers) { + if (client.isActive()) { + activeClientMembers.add(client); + } + } + return activeClientMembers; } - + private void validateActivationDate(final List 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); + final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, + GroupingTypesApiConstants.submittedOnDateParamName, this.submittedOnDate); dataValidationErrors.add(error); } @@ -638,11 +602,11 @@ public final class Group extends AbstractPersistable { } } - private void validateStatusNotEqualToActiveAndLogError(final List dataValidationErrors){ - + private void validateStatusNotEqualToActiveAndLogError(final List dataValidationErrors) { + if (isActive()) { final String defaultUserMessage = "Cannot activate group. Group is already active."; - final String globalisationMessageCode = "error.msg.group.already.active"; + final String globalisationMessageCode = "error.msg.group.already.active"; final ApiParameterError error = ApiParameterError.parameterError(globalisationMessageCode, defaultUserMessage, GroupingTypesApiConstants.activeParamName, true); dataValidationErrors.add(error); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupLevel.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupLevel.java index 280246aa5..53d3c7e17 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupLevel.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/domain/GroupLevel.java @@ -30,12 +30,6 @@ public class GroupLevel extends AbstractPersistable { @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; @@ -43,22 +37,17 @@ public class GroupLevel extends AbstractPersistable { 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 Long minClients, final Long maxClients) { + final boolean canHaveClients) { this.superParent = isSuperParent; this.parentId = parentId; this.levelName = levelName; this.recursable = recursable; this.canHaveClients = canHaveClients; - this.minClients = minClients; - this.maxClients = maxClients; - } public Long getParentId() { @@ -88,12 +77,9 @@ public class GroupLevel extends AbstractPersistable { public boolean isCenter() { return this.levelName.equalsIgnoreCase("Center"); } - - public Long getMinClients(){ - return this.minClients; - } - - public Long getMaxClients(){ - return this.maxClients; + + public boolean isGroup() { + return this.levelName.equalsIgnoreCase("Group"); } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java new file mode 100644 index 000000000..db2259824 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/exception/GroupMemberCountNotInPermissibleRangeException.java @@ -0,0 +1,17 @@ +/** + * 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.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class GroupMemberCountNotInPermissibleRangeException extends AbstractPlatformDomainRuleException { + + public GroupMemberCountNotInPermissibleRangeException(final Long groupId, final Integer minClients, final Integer maxClients) { + super("error.msg.group.members.count.must.be.in.permissible.range", "Number of members in the group with Id " + groupId + + " should be between " + minClients + " and " + maxClients, groupId, minClients, maxClients); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java index f013a56c1..6af988fb8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/group/service/GroupingTypesWritePlatformServiceJpaRepositoryImpl.java @@ -19,6 +19,7 @@ import org.mifosplatform.commands.service.CommandProcessingService; import org.mifosplatform.commands.service.CommandWrapperBuilder; import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; +import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.api.JsonCommand; import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; @@ -47,6 +48,7 @@ import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.GroupTypes; import org.mifosplatform.portfolio.group.exception.GroupAccountExistsException; import org.mifosplatform.portfolio.group.exception.GroupHasNoStaffException; +import org.mifosplatform.portfolio.group.exception.GroupMemberCountNotInPermissibleRangeException; import org.mifosplatform.portfolio.group.exception.GroupMustBePendingToBeDeletedException; import org.mifosplatform.portfolio.group.exception.InvalidGroupLevelException; import org.mifosplatform.portfolio.group.exception.InvalidGroupStateTransitionException; @@ -85,6 +87,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group private final SavingsAccountRepository savingsRepository; private final CommandProcessingService commandProcessingService; private final CalendarInstanceRepository calendarInstanceRepository; + private final ConfigurationDomainService configurationDomainService; @Autowired public GroupingTypesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -93,7 +96,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final GroupLevelRepository groupLevelRepository, final GroupingTypesDataValidator fromApiJsonDeserializer, final LoanRepository loanRepository, final SavingsAccountRepository savingsRepository, final CodeValueRepositoryWrapper codeValueRepository, final CommandProcessingService commandProcessingService, - final CalendarInstanceRepository calendarInstanceRepository) { + final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService) { this.context = context; this.groupRepository = groupRepository; this.clientRepositoryWrapper = clientRepositoryWrapper; @@ -107,6 +110,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group this.codeValueRepository = codeValueRepository; this.commandProcessingService = commandProcessingService; this.calendarInstanceRepository = calendarInstanceRepository; + this.configurationDomainService = configurationDomainService; } private CommandProcessingResult createGroupingType(final JsonCommand command, final GroupTypes groupingType, final Long centerId) { @@ -157,6 +161,11 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group boolean rollbackTransaction = false; if (newGroup.isActive()) { + // validate Group creation rules for Group + if (newGroup.isGroup()) { + validateGroupRulesBeforeActivation(newGroup); + } + if (newGroup.isCenter()) { final CommandWrapper commandWrapper = new CommandWrapperBuilder().activateCenter(null).build(); rollbackTransaction = this.commandProcessingService.validateCommand(commandWrapper, currentUser); @@ -226,6 +235,10 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final Group group = this.groupRepository.findOneWithNotFoundDetection(groupId); + if (group.isGroup()) { + validateGroupRulesBeforeActivation(group); + } + final LocalDate activationDate = command.localDateValueOfParameterNamed("activationDate"); validateOfficeOpeningDateisAfterGroupOrCenterOpeningDate(group.getOffice(), group.getGroupLevel(), activationDate); @@ -245,6 +258,20 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group } } + private void validateGroupRulesBeforeActivation(final Group group) { + Integer minClients = configurationDomainService.retrieveMinAllowedClientsInGroup(); + Integer maxClients = configurationDomainService.retrieveMaxAllowedClientsInGroup(); + boolean isGroupClientCountValid = group.isGroupsClientCountWithinMinMaxRange(minClients, maxClients); + if (!isGroupClientCountValid) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minClients, maxClients); } + } + + public void validateGroupRulesBeforeClientAssociation(final Group group) { + Integer minClients = configurationDomainService.retrieveMinAllowedClientsInGroup(); + Integer maxClients = configurationDomainService.retrieveMaxAllowedClientsInGroup(); + boolean isGroupClientCountValid = group.isGroupsClientCountWithinMaxRange(maxClients); + if (!isGroupClientCountValid) { throw new GroupMemberCountNotInPermissibleRangeException(group.getId(), minClients, maxClients); } + } + @Transactional @Override public CommandProcessingResult updateCenter(final Long centerId, final JsonCommand command) { @@ -645,6 +672,10 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group final Map actualChanges = new HashMap<>(); final List changes = groupForUpdate.associateClients(clientMembers); + + if (groupForUpdate.isGroup()) { + validateGroupRulesBeforeClientAssociation(groupForUpdate); + } if (!changes.isEmpty()) { actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes); } diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql new file mode 100644 index 000000000..16906cf4a --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V208__min_max_clients_in_group_redux.sql @@ -0,0 +1,9 @@ +INSERT INTO `c_configuration` (`id`, `name`,`value`, `enabled`, `description`) +VALUES (NULL, 'min-clients-in-group', '5', '0',"Minimum number of Clients that a Group should have"); + +INSERT INTO `c_configuration` (`id`, `name`, `value`, `enabled`, `description`) +VALUES (NULL, 'max-clients-in-group', '5', '0', "Maximum number of Clients that a Group can have"); + +ALTER TABLE `m_group_level` + DROP COLUMN `min_clients`, + DROP COLUMN `max_clients`;