ItEr09S09AdministracionGrupos: It allows to add and remove workers for a criterion which its type allows multiple active values.
This commit is contained in:
parent
5d874c61df
commit
ca86c14065
7 changed files with 196 additions and 80 deletions
|
|
@ -187,13 +187,14 @@ public class CriterionAdminController extends GenericForwardComposer {
|
||||||
onlyOneVisible = new OnlyOneVisible(listing, editComponent,
|
onlyOneVisible = new OnlyOneVisible(listing, editComponent,
|
||||||
createComponent, workersComponent);
|
createComponent, workersComponent);
|
||||||
onlyOneVisible.showOnly(listing);
|
onlyOneVisible.showOnly(listing);
|
||||||
comp.setVariable("controller", this, true);
|
comp.setVariable("controller", this, false);
|
||||||
|
workers = new CriterionWorkersController(criterionsModel);
|
||||||
|
workers.doAfterCompose(comp.getFellow("workersComponent"));
|
||||||
messagesForUser = new MessagesForUser(messagesContainer);
|
messagesForUser = new MessagesForUser(messagesContainer);
|
||||||
listing = (Grid) comp.getFellow("listing");
|
listing = (Grid) comp.getFellow("listing");
|
||||||
reload();
|
reload();
|
||||||
listing.setRowRenderer(getRowRenderer());
|
listing.setRowRenderer(getRowRenderer());
|
||||||
edition = new CriterionEditController(criterionsModel);
|
edition = new CriterionEditController(criterionsModel);
|
||||||
workers = new CriterionWorkersController(criterionsModel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reload() {
|
private void reload() {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package org.navalplanner.web.resources.criterion;
|
package org.navalplanner.web.resources.criterion;
|
||||||
|
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.navalplanner.business.resources.entities.Criterion;
|
||||||
import org.zkoss.zk.ui.util.GenericForwardComposer;
|
import org.zkoss.zk.ui.util.GenericForwardComposer;
|
||||||
|
|
||||||
|
|
||||||
public class CriterionEditController extends GenericForwardComposer {
|
public class CriterionEditController extends GenericForwardComposer {
|
||||||
|
|
||||||
private final ICriterionsModel criterionsModel;
|
private final ICriterionsModel criterionsModel;
|
||||||
|
|
@ -12,25 +14,11 @@ public class CriterionEditController extends GenericForwardComposer {
|
||||||
this.criterionsModel = criterionsModel;
|
this.criterionsModel = criterionsModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCriterionName(String name) {
|
public Criterion getCriterion() {
|
||||||
criterionsModel.setNameForCriterion(name);
|
return criterionsModel.getCriterion();
|
||||||
}
|
|
||||||
|
|
||||||
public String getCriterionName() {
|
|
||||||
return criterionsModel.getNameForCriterion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditing() {
|
public boolean isEditing() {
|
||||||
return criterionsModel.isEditing();
|
return criterionsModel.isEditing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCriterionActive() {
|
|
||||||
return criterionsModel.isCriterionActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCriterionActive(boolean active) {
|
|
||||||
criterionsModel.setCriterionActive(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,51 @@
|
||||||
package org.navalplanner.web.resources.criterion;
|
package org.navalplanner.web.resources.criterion;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.navalplanner.business.resources.entities.Resource;
|
||||||
import org.navalplanner.business.resources.entities.Worker;
|
import org.navalplanner.business.resources.entities.Worker;
|
||||||
|
import org.navalplanner.web.common.Util;
|
||||||
|
import org.zkoss.zk.ui.Component;
|
||||||
|
import org.zkoss.zk.ui.event.Event;
|
||||||
|
import org.zkoss.zk.ui.event.EventListener;
|
||||||
import org.zkoss.zk.ui.util.GenericForwardComposer;
|
import org.zkoss.zk.ui.util.GenericForwardComposer;
|
||||||
|
import org.zkoss.zul.ListModelList;
|
||||||
|
import org.zkoss.zul.Listcell;
|
||||||
|
import org.zkoss.zul.ListitemRenderer;
|
||||||
|
import org.zkoss.zul.api.Button;
|
||||||
|
import org.zkoss.zul.api.Listbox;
|
||||||
|
import org.zkoss.zul.api.Listitem;
|
||||||
|
import org.zkoss.zul.api.Window;
|
||||||
|
|
||||||
public class CriterionWorkersController extends GenericForwardComposer {
|
public class CriterionWorkersController extends GenericForwardComposer {
|
||||||
|
|
||||||
private final ICriterionsModel criterionsModel;
|
private final ICriterionsModel criterionsModel;
|
||||||
|
|
||||||
|
private Window workersWindow;
|
||||||
|
|
||||||
|
private Listbox list;
|
||||||
|
|
||||||
|
private Button saveListButton;
|
||||||
|
|
||||||
|
private Button cancelListButton;
|
||||||
|
|
||||||
|
public void showList(Event event) {
|
||||||
|
loadDataToList();
|
||||||
|
try {
|
||||||
|
workersWindow.doModal();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Worker> getAllWorkers() {
|
||||||
|
return criterionsModel.getAllWorkers();
|
||||||
|
}
|
||||||
|
|
||||||
public CriterionWorkersController(ICriterionsModel criterionsModel) {
|
public CriterionWorkersController(ICriterionsModel criterionsModel) {
|
||||||
this.criterionsModel = criterionsModel;
|
this.criterionsModel = criterionsModel;
|
||||||
}
|
}
|
||||||
|
|
@ -18,4 +55,76 @@ public class CriterionWorkersController extends GenericForwardComposer {
|
||||||
.getResourcesSatisfyingCurrentCriterionOfType(Worker.class);
|
.getResourcesSatisfyingCurrentCriterionOfType(Worker.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isChangeAssignmentsDisabled() {
|
||||||
|
return criterionsModel.isChangeAssignmentsDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doAfterCompose(final Component comp) throws Exception {
|
||||||
|
super.doAfterCompose(comp);
|
||||||
|
list = (Listbox) workersWindow.getFellow("list");
|
||||||
|
loadDataToList();
|
||||||
|
saveListButton = (Button) workersWindow.getFellow("saveList");
|
||||||
|
cancelListButton = (Button) workersWindow.getFellow("cancelList");
|
||||||
|
saveListButton.addEventListener("onClick", new EventListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(Event event) throws Exception {
|
||||||
|
Collection<? extends Listitem> items = (Collection<? extends Listitem>) list
|
||||||
|
.getItems();
|
||||||
|
List<Worker> selectedWorkers = new ArrayList<Worker>();
|
||||||
|
List<Worker> unSelectedWorkers = new ArrayList<Worker>();
|
||||||
|
for (Listitem listitem : items) {
|
||||||
|
if (listitem.isSelected()) {
|
||||||
|
selectedWorkers.add((Worker) listitem.getValue());
|
||||||
|
} else {
|
||||||
|
unSelectedWorkers.add((Worker) listitem.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
criterionsModel.activateAll(selectedWorkers);
|
||||||
|
criterionsModel.deactivateAll(unSelectedWorkers);
|
||||||
|
workersWindow.setVisible(false);
|
||||||
|
Util.reloadBindings(comp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cancelListButton.addEventListener("onClick", new EventListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvent(Event event) throws Exception {
|
||||||
|
workersWindow.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadDataToList() {
|
||||||
|
List<Worker> allWorkers = getAllWorkers();
|
||||||
|
final HashSet<Long> workersForCurrentCriterionIds = new HashSet<Long>(
|
||||||
|
asIds(getWorkersForCurrentCriterion()));
|
||||||
|
list.setModel(new ListModelList(allWorkers));
|
||||||
|
list.setItemRenderer(new ListitemRenderer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(org.zkoss.zul.Listitem item, Object data)
|
||||||
|
throws Exception {
|
||||||
|
Resource r = (Resource) data;
|
||||||
|
item.setValue(data);
|
||||||
|
item.setSelected(workersForCurrentCriterionIds.contains(r
|
||||||
|
.getId()));
|
||||||
|
Listcell cell = new Listcell();
|
||||||
|
cell.setParent(item);
|
||||||
|
Worker worker = (Worker) data;
|
||||||
|
cell.setLabel(worker.getSurname() + ", "
|
||||||
|
+ worker.getFirstName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Long> asIds(Collection<? extends Resource> resources) {
|
||||||
|
HashSet<Long> result = new HashSet<Long>();
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
result.add(resource.getId());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@ import java.util.List;
|
||||||
import org.apache.commons.lang.Validate;
|
import org.apache.commons.lang.Validate;
|
||||||
import org.hibernate.validator.ClassValidator;
|
import org.hibernate.validator.ClassValidator;
|
||||||
import org.hibernate.validator.InvalidValue;
|
import org.hibernate.validator.InvalidValue;
|
||||||
|
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
|
||||||
import org.navalplanner.business.common.exceptions.ValidationException;
|
import org.navalplanner.business.common.exceptions.ValidationException;
|
||||||
import org.navalplanner.business.resources.bootstrap.ICriterionsBootstrap;
|
import org.navalplanner.business.resources.bootstrap.ICriterionsBootstrap;
|
||||||
import org.navalplanner.business.resources.entities.Criterion;
|
import org.navalplanner.business.resources.entities.Criterion;
|
||||||
|
import org.navalplanner.business.resources.entities.CriterionWithItsType;
|
||||||
import org.navalplanner.business.resources.entities.ICriterionType;
|
import org.navalplanner.business.resources.entities.ICriterionType;
|
||||||
import org.navalplanner.business.resources.entities.Resource;
|
import org.navalplanner.business.resources.entities.Resource;
|
||||||
import org.navalplanner.business.resources.entities.Worker;
|
import org.navalplanner.business.resources.entities.Worker;
|
||||||
import org.navalplanner.business.resources.services.CriterionService;
|
import org.navalplanner.business.resources.services.CriterionService;
|
||||||
|
import org.navalplanner.business.resources.services.ResourceService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
|
@ -37,12 +40,13 @@ public class CriterionsModel implements ICriterionsModel {
|
||||||
@Autowired
|
@Autowired
|
||||||
private CriterionService criterionService;
|
private CriterionService criterionService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceService resourceService;
|
||||||
|
|
||||||
private ICriterionType<?> criterionType;
|
private ICriterionType<?> criterionType;
|
||||||
|
|
||||||
private Criterion criterion;
|
private Criterion criterion;
|
||||||
|
|
||||||
private String nameForCriterion;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<ICriterionType<?>> getTypes() {
|
public List<ICriterionType<?>> getTypes() {
|
||||||
|
|
@ -60,15 +64,11 @@ public class CriterionsModel implements ICriterionsModel {
|
||||||
return criterion;
|
return criterion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setNameForCriterion(String name) {
|
|
||||||
this.nameForCriterion = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareForCreate(ICriterionType<?> criterionType) {
|
public void prepareForCreate(ICriterionType<?> criterionType) {
|
||||||
this.criterionType = criterionType;
|
this.criterionType = criterionType;
|
||||||
this.criterion = null;
|
this.criterion = (Criterion) criterionType
|
||||||
|
.createCriterionWithoutNameYet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -90,42 +90,13 @@ public class CriterionsModel implements ICriterionsModel {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void saveCriterion() throws ValidationException {
|
public void saveCriterion() throws ValidationException {
|
||||||
if (criterionType != null) {
|
|
||||||
create();
|
|
||||||
} else {
|
|
||||||
saveEdit();
|
|
||||||
}
|
|
||||||
criterion = null;
|
|
||||||
criterionType = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveEdit() {
|
|
||||||
criterionService.save(criterion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void create() throws ValidationException {
|
|
||||||
Criterion criterion = (Criterion) criterionType
|
|
||||||
.createCriterion(nameForCriterion);
|
|
||||||
InvalidValue[] invalidValues = criterionValidator
|
InvalidValue[] invalidValues = criterionValidator
|
||||||
.getInvalidValues(criterion);
|
.getInvalidValues(criterion);
|
||||||
if (invalidValues.length > 0)
|
if (invalidValues.length > 0)
|
||||||
throw new ValidationException(invalidValues);
|
throw new ValidationException(invalidValues);
|
||||||
criterionService.save(criterion);
|
criterionService.save(criterion);
|
||||||
}
|
criterion = null;
|
||||||
|
criterionType = null;
|
||||||
@Override
|
|
||||||
public String getNameForCriterion() {
|
|
||||||
if (criterion == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return criterion.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCriterionActive() {
|
|
||||||
if (criterion == null)
|
|
||||||
return false;
|
|
||||||
return criterion.isActive();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -133,11 +104,6 @@ public class CriterionsModel implements ICriterionsModel {
|
||||||
return criterion != null;
|
return criterion != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCriterionActive(boolean active) {
|
|
||||||
criterion.setActive(active);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplyableToWorkers(Criterion criterion) {
|
public boolean isApplyableToWorkers(Criterion criterion) {
|
||||||
ICriterionType<?> type = getTypeFor(criterion);
|
ICriterionType<?> type = getTypeFor(criterion);
|
||||||
|
|
@ -151,4 +117,48 @@ public class CriterionsModel implements ICriterionsModel {
|
||||||
return new ArrayList<T>();
|
return new ArrayList<T>();
|
||||||
return criterionService.getResourcesSatisfying(klass, criterion);
|
return criterionService.getResourcesSatisfying(klass, criterion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Worker> getAllWorkers() {
|
||||||
|
return resourceService.getWorkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChangeAssignmentsDisabled() {
|
||||||
|
return criterionType == null
|
||||||
|
|| !criterionType.allowMultipleActiveCriterionsPerResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void activateAll(Collection<? extends Resource> resources) {
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
Resource reloaded = find(resource.getId());
|
||||||
|
reloaded
|
||||||
|
.activate(new CriterionWithItsType(criterionType, criterion));
|
||||||
|
resourceService.saveResource(reloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void deactivateAll(Collection<? extends Resource> resources) {
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
Resource reloaded = find(resource.getId());
|
||||||
|
reloaded.deactivate(new CriterionWithItsType(criterionType,
|
||||||
|
criterion));
|
||||||
|
resourceService.saveResource(reloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resource find(Long id) {
|
||||||
|
Resource reloaded;
|
||||||
|
try {
|
||||||
|
reloaded = resourceService.findResource(id);
|
||||||
|
} catch (InstanceNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return reloaded;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import org.navalplanner.business.common.exceptions.ValidationException;
|
||||||
import org.navalplanner.business.resources.entities.Criterion;
|
import org.navalplanner.business.resources.entities.Criterion;
|
||||||
import org.navalplanner.business.resources.entities.ICriterionType;
|
import org.navalplanner.business.resources.entities.ICriterionType;
|
||||||
import org.navalplanner.business.resources.entities.Resource;
|
import org.navalplanner.business.resources.entities.Resource;
|
||||||
|
import org.navalplanner.business.resources.entities.Worker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CriterionsModel contract <br />
|
* CriterionsModel contract <br />
|
||||||
|
|
@ -26,21 +27,21 @@ public interface ICriterionsModel {
|
||||||
|
|
||||||
ICriterionType<?> getTypeFor(Criterion criterion);
|
ICriterionType<?> getTypeFor(Criterion criterion);
|
||||||
|
|
||||||
String getNameForCriterion();
|
|
||||||
|
|
||||||
void setNameForCriterion(String name);
|
|
||||||
|
|
||||||
void saveCriterion() throws ValidationException;
|
void saveCriterion() throws ValidationException;
|
||||||
|
|
||||||
boolean isEditing();
|
boolean isEditing();
|
||||||
|
|
||||||
boolean isCriterionActive();
|
|
||||||
|
|
||||||
void setCriterionActive(boolean active);
|
|
||||||
|
|
||||||
boolean isApplyableToWorkers(Criterion criterion);
|
boolean isApplyableToWorkers(Criterion criterion);
|
||||||
|
|
||||||
<T extends Resource> List<T> getResourcesSatisfyingCurrentCriterionOfType(
|
<T extends Resource> List<T> getResourcesSatisfyingCurrentCriterionOfType(
|
||||||
Class<T> klass);
|
Class<T> klass);
|
||||||
|
|
||||||
|
List<Worker> getAllWorkers();
|
||||||
|
|
||||||
|
boolean isChangeAssignmentsDisabled();
|
||||||
|
|
||||||
|
void activateAll(Collection<? extends Resource> selected);
|
||||||
|
|
||||||
|
void deactivateAll(Collection<? extends Resource> unSelectedWorkers);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<vbox id="${arg.top_id}">
|
<vbox id="${arg.top_id}">
|
||||||
<hbox>
|
<hbox>
|
||||||
<label value="name" />
|
<label value="name" />
|
||||||
<textbox value="@{controller.edition.criterionName}" />
|
<textbox value="@{controller.edition.criterion.name}" />
|
||||||
<checkbox checked="@{controller.edition.criterionActive}"
|
<checkbox checked="@{controller.edition.criterion.active}"
|
||||||
visible="@{controller.edition.editing}">
|
visible="@{controller.edition.editing}">
|
||||||
</checkbox>
|
</checkbox>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<zk>
|
<vbox id="${arg.top_id}" apply="${controller.workers}">
|
||||||
<grid model="@{controller.workers.workersForCurrentCriterion}"
|
<grid model="@{controller.workers.workersForCurrentCriterion}"
|
||||||
id="${arg.top_id}" mold="paging" pageSize="5">
|
mold="paging" pageSize="5" apply="${controller.workers}">
|
||||||
<columns>
|
<columns>
|
||||||
<column label="operations" />
|
|
||||||
<column label="First Name" sort="auto(firstName)" />
|
<column label="First Name" sort="auto(firstName)" />
|
||||||
<column label="Surname" sort="auto(surname)" />
|
<column label="Surname" sort="auto(surname)" />
|
||||||
<column label="nif" sort="auto(nif)" />
|
<column label="nif" sort="auto(nif)" />
|
||||||
|
|
@ -12,12 +11,20 @@
|
||||||
<label value="@{worker.firstName}" />
|
<label value="@{worker.firstName}" />
|
||||||
<label value="@{worker.surname}" />
|
<label value="@{worker.surname}" />
|
||||||
<label value="@{worker.nif}" />
|
<label value="@{worker.nif}" />
|
||||||
<hbox>
|
|
||||||
<button label="Edit"></button>
|
|
||||||
</hbox>
|
|
||||||
</row>
|
</row>
|
||||||
</rows>
|
</rows>
|
||||||
</grid>
|
</grid>
|
||||||
|
|
||||||
|
<button id="adminButton" label="Administrar" disabled="@{controller.workers.changeAssignmentsDisabled}" onClick="controller.workers.showList(event);"></button>
|
||||||
|
<window visible="false" id="workersWindow">
|
||||||
|
<listbox id="list" model="@{controller.workers.allWorkers}" mold="paging" multiple="true" checkmark="true">
|
||||||
|
<listhead>
|
||||||
|
<listheader label="Name" sort="auto(surname)"></listheader>
|
||||||
|
</listhead>
|
||||||
|
</listbox>
|
||||||
|
<button label="Gardar" id="saveList"></button>
|
||||||
|
<button label="Cancelar" id="cancelList"></button>
|
||||||
|
</window>
|
||||||
<button onClick="controller.cancel();"
|
<button onClick="controller.cancel();"
|
||||||
label="${arg.cancel_button_label}" />
|
label="${arg.cancel_button_label}" />
|
||||||
</zk>
|
</vbox>
|
||||||
Loading…
Add table
Reference in a new issue