Merge remote-tracking branch 'ashok/MIFOSX-334'

This commit is contained in:
Keith Woodlock
2013-05-05 13:09:17 +01:00
15 changed files with 300 additions and 13 deletions
@@ -0,0 +1,4 @@
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'ASSOCIATECLIENTS_GROUP', 'GROUP', 'ASSOCIATECLIENTS', 0);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'DISASSOCIATECLIENTS_GROUP', 'GROUP', 'DISASSOCIATECLIENTS', 0);
@@ -498,4 +498,12 @@ public class CommandWrapper {
public boolean isReportResource() { public boolean isReportResource() {
return this.entityName.equalsIgnoreCase("REPORT"); return this.entityName.equalsIgnoreCase("REPORT");
} }
public boolean isAssociateClients(){
return this.actionName.equalsIgnoreCase("ASSOCIATECLIENTS");
}
public boolean isDisassociateClients(){
return this.actionName.equalsIgnoreCase("DISASSOCIATECLIENTS");
}
} }
@@ -857,6 +857,24 @@ public class CommandWrapperBuilder {
return this; return this;
} }
public CommandWrapperBuilder associateClientsToGroup(final Long groupId) {
this.actionName = "ASSOCIATECLIENTS";
this.entityName = "GROUP";
this.entityId = groupId;
this.groupId = groupId;
this.href = "/groups/" + groupId + "?command=associateClients";
return this;
}
public CommandWrapperBuilder disassociateClientsFromGroup(final Long groupId) {
this.actionName = "DISASSOCIATECLIENTS";
this.entityName = "GROUP";
this.entityId = groupId;
this.groupId = groupId;
this.href = "/groups/" + groupId + "?command=disassociateClients";
return this;
}
public CommandWrapperBuilder unassignStaff(final Long groupId) { public CommandWrapperBuilder unassignStaff(final Long groupId) {
this.actionName = "UNASSIGNSTAFF"; this.actionName = "UNASSIGNSTAFF";
this.entityName = "GROUP"; this.entityName = "GROUP";
@@ -400,6 +400,10 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
handler = applicationContext.getBean("deleteGroupCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("deleteGroupCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isGroupActivation()) { } else if (wrapper.isGroupActivation()) {
handler = applicationContext.getBean("activateGroupCommandHandler", NewCommandSourceHandler.class); handler = applicationContext.getBean("activateGroupCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isAssociateClients()) {
handler = applicationContext.getBean("associateClientsToGroupCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDisassociateClients()) {
handler = applicationContext.getBean("disassociateClientsFromGroupCommandHandler", NewCommandSourceHandler.class);
} else { } else {
throw new UnsupportedCommandException(wrapper.commandName()); throw new UnsupportedCommandException(wrapper.commandName());
} }
@@ -223,6 +223,12 @@ public class GroupsApiResource {
if (is(commandParam, "activate")) { if (is(commandParam, "activate")) {
final CommandWrapper commandRequest = builder.activateGroup(groupId).build(); final CommandWrapper commandRequest = builder.activateGroup(groupId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if (is(commandParam, "associateClients")) {
final CommandWrapper commandRequest = builder.associateClientsToGroup(groupId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if (is(commandParam, "disassociateClients")) {
final CommandWrapper commandRequest = builder.disassociateClientsFromGroup(groupId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} }
if (result == null) { if (result == null) {
@@ -39,6 +39,8 @@ import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants; import org.mifosplatform.portfolio.group.api.GroupingTypesApiConstants;
import org.mifosplatform.portfolio.group.exception.ClientExistInGroupException;
import org.mifosplatform.portfolio.group.exception.ClientNotInGroupException;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -312,6 +314,33 @@ public final class Group extends AbstractPersistable<Long> {
return differences; return differences;
} }
public List<String> associateClients(final Set<Client> clientMembersSet) {
List<String> differences = new ArrayList<String>();
for (Client client : clientMembersSet) {
if(this.hasClientAsMember(client)){
throw new ClientExistInGroupException(client.getId(), this.getId());
}
this.clientMembers.add(client);
differences.add(client.getId().toString());
}
return differences;
}
public List<String> disassociateClients(final Set<Client> clientMembersSet) {
List<String> differences = new ArrayList<String>();
for (Client client : clientMembersSet) {
if(this.hasClientAsMember(client)){
this.clientMembers.remove(client);
differences.add(client.getId().toString());
}else{
throw new ClientNotInGroupException(client.getId(), this.getId());
}
}
return differences;
}
private String[] getClientIds(final Set<Client> clients) { private String[] getClientIds(final Set<Client> clients) {
String[] clientIds = new String[clients.size()]; String[] clientIds = new String[clients.size()];
Iterator<Client> it = clients.iterator(); Iterator<Client> it = clients.iterator();
@@ -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 ClientExistInGroupException extends AbstractPlatformDomainRuleException {
public ClientExistInGroupException(final Long clientId, final Long groupId) {
super("error.msg.group.client.exist.in.group", "Client with identifier " + clientId + " is already exists in Group with identifier " + groupId,
clientId, groupId);
}
}
@@ -0,0 +1,15 @@
/**
* 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 GroupLoanExistsException extends AbstractPlatformDomainRuleException {
public GroupLoanExistsException(final String action, String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg.group." + action + "." + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,32 @@
/**
* 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.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.group.service.GroupingTypesWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AssociateClientsToGroupCommandHandler implements NewCommandSourceHandler {
private final GroupingTypesWritePlatformService writePlatformService;
@Autowired
public AssociateClientsToGroupCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
this.writePlatformService = clientWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.associateClientsToGroup(command.entityId(), command);
}
}
@@ -0,0 +1,31 @@
/**
* 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.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.group.service.GroupingTypesWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DisassociateClientsFromGroupCommandHandler implements NewCommandSourceHandler {
private final GroupingTypesWritePlatformService writePlatformService;
@Autowired
public DisassociateClientsFromGroupCommandHandler(final GroupingTypesWritePlatformService clientWritePlatformService) {
this.writePlatformService = clientWritePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.disassociateClientsFromGroup(command.entityId(), command);
}
}
@@ -344,4 +344,44 @@ public final class GroupingTypesDataValidator {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
public void validateForAssociateClients(final String json){
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList(GroupingTypesApiConstants.clientMembersParamName));
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
final String[] clients = fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.clientMembersParamName, element);
baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientMembersParamName).value(clients).arrayNotEmpty();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForDisassociateClients(final String json){
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList(GroupingTypesApiConstants.clientMembersParamName));
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParameters);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("group");
final String[] clients = fromApiJsonHelper.extractArrayNamed(GroupingTypesApiConstants.clientMembersParamName, element);
baseDataValidator.reset().parameter(GroupingTypesApiConstants.clientMembersParamName).value(clients).arrayNotEmpty();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
} }
@@ -22,5 +22,9 @@ public interface GroupingTypesWritePlatformService {
CommandProcessingResult deleteGroup(Long groupId); CommandProcessingResult deleteGroup(Long groupId);
CommandProcessingResult unassignStaff(Long grouptId, JsonCommand command); CommandProcessingResult unassignStaff(Long groupId, JsonCommand command);
CommandProcessingResult associateClientsToGroup(Long groupId, JsonCommand command);
CommandProcessingResult disassociateClientsFromGroup(Long groupId, JsonCommand command);
} }
@@ -5,6 +5,8 @@
*/ */
package org.mifosplatform.portfolio.group.service; package org.mifosplatform.portfolio.group.service;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -35,9 +37,12 @@ import org.mifosplatform.portfolio.group.domain.GroupLevelRepository;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.group.domain.GroupTypes; import org.mifosplatform.portfolio.group.domain.GroupTypes;
import org.mifosplatform.portfolio.group.exception.GroupHasNoStaffException; import org.mifosplatform.portfolio.group.exception.GroupHasNoStaffException;
import org.mifosplatform.portfolio.group.exception.GroupLoanExistsException;
import org.mifosplatform.portfolio.group.exception.GroupMustBePendingToBeDeletedException; import org.mifosplatform.portfolio.group.exception.GroupMustBePendingToBeDeletedException;
import org.mifosplatform.portfolio.group.exception.InvalidGroupLevelException; import org.mifosplatform.portfolio.group.exception.InvalidGroupLevelException;
import org.mifosplatform.portfolio.group.serialization.GroupingTypesDataValidator; import org.mifosplatform.portfolio.group.serialization.GroupingTypesDataValidator;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository;
import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -46,6 +51,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@Service @Service
@@ -61,12 +67,14 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final GroupLevelRepository groupLevelRepository; private final GroupLevelRepository groupLevelRepository;
private final GroupingTypesDataValidator fromApiJsonDeserializer; private final GroupingTypesDataValidator fromApiJsonDeserializer;
private final LoanRepository loanRepository;
@Autowired @Autowired
public GroupingTypesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public GroupingTypesWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
final GroupRepositoryWrapper groupRepository, final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, final ClientRepositoryWrapper clientRepository,
final OfficeRepository officeRepository, final StaffRepositoryWrapper staffRepository, final NoteRepository noteRepository, final OfficeRepository officeRepository, final StaffRepositoryWrapper staffRepository, final NoteRepository noteRepository,
final GroupLevelRepository groupLevelRepository, final GroupingTypesDataValidator fromApiJsonDeserializer) { final GroupLevelRepository groupLevelRepository, final GroupingTypesDataValidator fromApiJsonDeserializer,
final LoanRepository loanRepository) {
this.context = context; this.context = context;
this.groupRepository = groupRepository; this.groupRepository = groupRepository;
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
@@ -75,6 +83,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
this.noteRepository = noteRepository; this.noteRepository = noteRepository;
this.groupLevelRepository = groupLevelRepository; this.groupLevelRepository = groupLevelRepository;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanRepository = loanRepository;
} }
private CommandProcessingResult createGroupingType(final JsonCommand command, final GroupTypes groupingType, final Long centerId) { private CommandProcessingResult createGroupingType(final JsonCommand command, final GroupTypes groupingType, final Long centerId) {
@@ -101,7 +110,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
staff = this.staffRepository.findByOfficeWithNotFoundDetection(staffId, officeId); staff = this.staffRepository.findByOfficeWithNotFoundDetection(staffId, officeId);
} }
final Set<Client> clientMembers = assembleSetOfClients(officeId, command); final Set<Client> clientMembers = assembleSetOfClients(parentGroup, command);
final Set<Group> groupMembers = assembleSetOfChildGroups(officeId, command); final Set<Group> groupMembers = assembleSetOfChildGroups(officeId, command);
@@ -269,11 +278,11 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
} }
} }
final Set<Client> clientMembers = assembleSetOfClients(officeId, command); /*final Set<Client> clientMembers = assembleSetOfClients(officeId, command);
List<String> changes = groupForUpdate.updateClientMembersIfDifferent(clientMembers); List<String> changes = groupForUpdate.updateClientMembersIfDifferent(clientMembers);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes); actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
} }*/
this.groupRepository.saveAndFlush(groupForUpdate); this.groupRepository.saveAndFlush(groupForUpdate);
@@ -345,7 +354,7 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
.build(); .build();
} }
private Set<Client> assembleSetOfClients(final Long officeId, final JsonCommand command) { private Set<Client> assembleSetOfClients(final Group group, final JsonCommand command) {
final Set<Client> clientMembers = new HashSet<Client>(); final Set<Client> clientMembers = new HashSet<Client>();
final String[] clientMembersArray = command.arrayValueOfParameterNamed(GroupingTypesApiConstants.clientMembersParamName); final String[] clientMembersArray = command.arrayValueOfParameterNamed(GroupingTypesApiConstants.clientMembersParamName);
@@ -354,9 +363,9 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
for (final String clientId : clientMembersArray) { for (final String clientId : clientMembersArray) {
final Long id = Long.valueOf(clientId); final Long id = Long.valueOf(clientId);
final Client client = this.clientRepository.findOneWithNotFoundDetection(id); final Client client = this.clientRepository.findOneWithNotFoundDetection(id);
if (!client.isOfficeIdentifiedBy(officeId)) { if (!client.isOfficeIdentifiedBy(group.officeId())) {
final String errorMessage = "Group and Client must have the same office."; final String errorMessage = "Group with identifier " + group.getId() + " and Client with identifier " + clientId + " must have the same office.";
throw new InvalidOfficeException("client", "attach.to.group", errorMessage); throw new InvalidOfficeException("client", "attach.to.group", errorMessage, group.getId(), clientId);
} }
clientMembers.add(client); clientMembers.add(client);
} }
@@ -429,4 +438,69 @@ public class GroupingTypesWritePlatformServiceJpaRepositoryImpl implements Group
throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue", throw new PlatformDataIntegrityException("error.msg.group.unknown.data.integrity.issue",
"Unknown data integrity issue with resource."); "Unknown data integrity issue with resource.");
} }
@Override
public CommandProcessingResult associateClientsToGroup(Long groupId, JsonCommand command) {
this.fromApiJsonDeserializer.validateForAssociateClients(command.json());
final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
final Set<Client> clientMembers = assembleSetOfClients(groupForUpdate, command);
Map<String, Object> actualChanges = new HashMap<String, Object>();
List<String> changes = groupForUpdate.associateClients(clientMembers);
if (!changes.isEmpty()) {
actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
}
this.groupRepository.saveAndFlush(groupForUpdate);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withOfficeId(groupForUpdate.officeId()) //
.withGroupId(groupForUpdate.getId()) //
.withEntityId(groupForUpdate.getId()) //
.with(actualChanges) //
.build();
}
@Transactional
@Override
public CommandProcessingResult disassociateClientsFromGroup(Long groupId, JsonCommand command) {
this.fromApiJsonDeserializer.validateForDisassociateClients(command.json());
final Group groupForUpdate = this.groupRepository.findOneWithNotFoundDetection(groupId);
final Set<Client> clientMembers = assembleSetOfClients(groupForUpdate, command);
//check if any client has got group loans
validateForJLGLoan(groupForUpdate.getId(), clientMembers);
Map<String, Object> actualChanges = new HashMap<String, Object>();
List<String> changes = groupForUpdate.disassociateClients(clientMembers);
if (!changes.isEmpty()) {
actualChanges.put(GroupingTypesApiConstants.clientMembersParamName, changes);
}
this.groupRepository.saveAndFlush(groupForUpdate);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withOfficeId(groupForUpdate.officeId()) //
.withGroupId(groupForUpdate.getId()) //
.withEntityId(groupForUpdate.getId()) //
.with(actualChanges) //
.build();
}
@Transactional
private void validateForJLGLoan(Long groupId, Set<Client> clientMembers) {
for (Client client : clientMembers) {
Collection<Loan> loans = this.loanRepository.findByClientIdAndGroupId(client.getId(), groupId);
if(!CollectionUtils.isEmpty(loans)){
final String defaultUserMessage = "Client with identifier " + client.getId() + " cannot be disassociated it has group loans.";
throw new GroupLoanExistsException("disassociate", "client.has.group.loan", defaultUserMessage, client.getId(), groupId);
}
}
}
} }
@@ -89,11 +89,11 @@ public class Loan extends AbstractPersistable<Long> {
@Column(name = "external_id") @Column(name = "external_id")
private String externalId; private String externalId;
@ManyToOne(optional = true) @ManyToOne
@JoinColumn(name = "client_id", nullable = true) @JoinColumn(name = "client_id", nullable = true)
private Client client; private Client client;
@ManyToOne(optional = true) @ManyToOne
@JoinColumn(name = "group_id", nullable = true) @JoinColumn(name = "group_id", nullable = true)
private Group group; private Group group;
@@ -300,7 +300,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
protected Loan() { protected Loan() {
// this.client = null;
} }
private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund, final Staff loanOfficer, private Loan(final String accountNo, final Client client, final Group group, final Integer loanType, final Fund fund, final Staff loanOfficer,
@@ -5,10 +5,15 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.domain; package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface LoanRepository extends JpaRepository<Loan, Long>, public interface LoanRepository extends JpaRepository<Loan, Long>,
JpaSpecificationExecutor<Loan> { JpaSpecificationExecutor<Loan> {
// no added behaviour @Query("from Loan loan where loan.client.id = :clientId and loan.group.id = :groupId")
List<Loan> findByClientIdAndGroupId(@Param("clientId") Long clientId, @Param("groupId") Long groupId);
} }