diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionSatisfaction.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionSatisfaction.java index 3d3aaaf97..9e4cffceb 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionSatisfaction.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionSatisfaction.java @@ -4,6 +4,7 @@ import java.util.Comparator; import java.util.Date; import org.apache.commons.lang.Validate; +import org.apache.commons.lang.builder.ToStringBuilder; /** * Declares a interval of time in which the criterion is satisfied
@@ -63,6 +64,20 @@ public class CriterionSatisfaction { private Resource resource; + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + public CriterionSatisfaction copy() { + CriterionSatisfaction result = new CriterionSatisfaction(); + result.startDate = startDate; + result.finishDate = finishDate; + result.criterion = criterion; + result.resource = resource; + return result; + } + public Date getStartDate() { return startDate != null ? new Date(startDate.getTime()) : null; } @@ -119,20 +134,26 @@ public class CriterionSatisfaction { } public void setEndDate(Date date) { - if (date == null) { - finishDate = null; - } - if ((startDate.equals(date) || startDate.before(date))) - finishDate = date; + finishDate = date; } public void setStartDate(Date date) { - if ((finishDate == null || finishDate.after(date))) - startDate = date; + startDate = date; } public boolean overlapsWith(Interval interval) { return getInterval().overlapsWith(interval); } + public boolean goesBeforeWithoutOverlapping(CriterionSatisfaction other) { + int compare = BY_START_COMPARATOR.compare(this, other); + if (compare > 0) { + return false; + } else { + Interval thisInterval = getInterval(); + return !thisInterval.overlapsWith(other.getInterval()); + } + + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Interval.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Interval.java index 228b3d298..306993c3f 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Interval.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Interval.java @@ -109,6 +109,12 @@ class Range extends Interval { .compareTo(interval.start) > 0); } + @Override + public String toString() { + return new StringBuilder("[").append(start).append(", ").append(end) + .append(")").toString(); + } + } class OpenEndedInterval extends Interval { @@ -131,6 +137,11 @@ class OpenEndedInterval extends Interval { return start.before(interval.start) || interval.end == null || start.before(interval.end); } + + @Override + public String toString() { + return new StringBuilder("[").append(start).append(",...)").toString(); + } } class Point extends Interval { @@ -154,4 +165,10 @@ class Point extends Interval { return interval.contains(end) && !interval.start.equals(end); } + @Override + public String toString() { + return new StringBuilder().append("[").append(start).append(")") + .toString(); + } + } \ No newline at end of file diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java index 858fb47f0..fd6d2a6d5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java @@ -1,12 +1,14 @@ package org.navalplanner.business.resources.entities; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.ListIterator; import java.util.Set; import org.apache.commons.lang.Validate; @@ -142,6 +144,25 @@ public abstract class Resource { return new ArrayList(result); } + public Query oneOf(ICriterionType[] laboralRelatedTypes) { + return oneOf(Arrays.asList(laboralRelatedTypes)); + } + + public Query oneOf(final Collection> types) { + return withNewPredicate(new Predicate() { + + @Override + public boolean accepts(CriterionSatisfaction satisfaction) { + for (ICriterionType criterionType : types) { + if (criterionType.contains(satisfaction.getCriterion())) { + return true; + } + } + return false; + } + }); + } + } public Query query() { @@ -245,8 +266,8 @@ public abstract class Resource { } - public CriterionSatisfaction addSatisfaction( - ICriterionType type, CriterionSatisfaction satisfaction) { + public CriterionSatisfaction addSatisfaction(ICriterionType type, + CriterionSatisfaction satisfaction) { return new EnsureSatisfactionIsCorrect(this, type, satisfaction) .addSatisfaction(); } @@ -347,7 +368,8 @@ public abstract class Resource { public boolean canAddSatisfaction(ICriterionType type, CriterionSatisfaction satisfaction) { - return new EnsureSatisfactionIsCorrect(this, type, satisfaction) + EnsureSatisfactionIsCorrect ensureSatisfactionIsCorrect = new EnsureSatisfactionIsCorrect(this, type, satisfaction); + return ensureSatisfactionIsCorrect .canAddSatisfaction(); } @@ -371,8 +393,7 @@ public abstract class Resource { return previous; } - public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction) - throws InstanceNotFoundException { + public void removeCriterionSatisfaction(CriterionSatisfaction satisfaction) { criterionSatisfactions.remove(satisfaction); } @@ -380,4 +401,66 @@ public abstract class Resource { return criterionSatisfactions.contains(satisfaction); } + public void checkNotOverlaps(List> types) { + for (ICriterionType criterionType : types) { + if (!criterionType.allowSimultaneousCriterionsPerResource()) { + List satisfactions = query().from( + criterionType).sortByStartDate().result(); + ListIterator listIterator = satisfactions + .listIterator(); + while (listIterator.hasNext()) { + CriterionSatisfaction current = listIterator.next(); + CriterionSatisfaction previous = getPrevious(listIterator); + CriterionSatisfaction next = getNext(listIterator); + if (previous != null) { + checkNotOverlaps(previous, current); + } + if (next != null) + checkNotOverlaps(current, next); + } + } + + } + + } + + private void checkNotOverlaps(CriterionSatisfaction before, + CriterionSatisfaction after) { + if (!before.goesBeforeWithoutOverlapping(after)) { + throw new IllegalArgumentException(createOverlapsMessage(before, + after)); + } + } + + private String createOverlapsMessage(CriterionSatisfaction before, + CriterionSatisfaction after) { + return new StringBuilder("the satisfaction").append(before).append( + "overlaps with").append(after).toString(); + } + + private CriterionSatisfaction getNext( + ListIterator listIterator) { + if (listIterator.hasNext()) { + CriterionSatisfaction result = listIterator.next(); + listIterator.previous(); + return result; + } + return null; + } + + private CriterionSatisfaction getPrevious( + ListIterator listIterator) { + listIterator.previous(); + try { + if (listIterator.hasPrevious()) { + CriterionSatisfaction result = listIterator.previous(); + listIterator.next(); + return result; + } + return null; + } finally { + listIterator.next(); + } + } + } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java index c8cb71561..666cd1a28 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Set; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; -//import org.navalplanner.business.resources.entities.CriterionSatisfaction; +import org.navalplanner.business.common.exceptions.ValidationException; import org.navalplanner.business.resources.entities.ICriterion; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.Worker; @@ -19,6 +19,7 @@ public interface ResourceService { * It updates or inserts the resource passed as a parameter. If the resource * is a composite resource, updating or inserting is cascaded to the * resources contained in it. + * @throws ValidationException */ public void saveResource(Resource resource); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java index 42ac64e6c..4755888a4 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java @@ -5,9 +5,10 @@ import java.util.List; import java.util.Set; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.bootstrap.ICriterionsBootstrap; import org.navalplanner.business.resources.daos.IResourceDao; - import org.navalplanner.business.resources.entities.ICriterion; +import org.navalplanner.business.resources.entities.ICriterionType; import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.business.resources.services.ResourceService; @@ -25,10 +26,20 @@ public class ResourceServiceImpl implements ResourceService { @Autowired private IResourceDao resourceDao; + @Autowired + private ICriterionsBootstrap criterionsBootstrap; + + @Transactional public void saveResource(Resource resource) { + checkResourceIsOk(resource); resourceDao.save(resource); } + private void checkResourceIsOk(Resource resource) { + List> types = criterionsBootstrap.getTypes(); + resource.checkNotOverlaps(types); + } + @Transactional(readOnly = true) public Resource findResource(Long resourceId) throws InstanceNotFoundException { diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/entities/CriterionSatisfactionTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/entities/CriterionSatisfactionTest.java index 53096ce29..ca81d3616 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/entities/CriterionSatisfactionTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/entities/CriterionSatisfactionTest.java @@ -127,4 +127,37 @@ public class CriterionSatisfactionTest { assertThat(copy, equalTo(orderedSatisfactions)); } } + + @Test + public void testGoesBeforeWithoutOverlapping() { + final Criterion criterion = CriterionDAOTest.createValidCriterion(); + Worker worker = new Worker("firstName", "surName", "2333232", 10); + CriterionSatisfaction posterior = new CriterionSatisfaction(); + posterior.setCriterion(criterion); + posterior.setStartDate(year(2000)); + posterior.setEndDate(year(2008)); + Interval[] goesAfterOrOverlapsIntervals = { Interval.from(year(2000)), + Interval.from(year(2001)), Interval.from(year(1999)), + Interval.range(year(1999), year(2001)), + Interval.from(year(2009)), + Interval.range(year(2009), year(2012)) }; + for (Interval interval : goesAfterOrOverlapsIntervals) { + CriterionSatisfaction copied = posterior.copy(); + copied.setStartDate(interval.getStart()); + copied.setEndDate(interval.getEnd()); + assertFalse(interval + " shouldn't go before", copied + .goesBeforeWithoutOverlapping(posterior)); + } + Interval[] goesBeforeWithoutOverlappingInterval = { + Interval.point(year(2000)), + Interval.range(year(1990), year(2000)), + Interval.range(year(1990), year(1997)) }; + for (Interval interval : goesBeforeWithoutOverlappingInterval) { + CriterionSatisfaction copied = posterior.copy(); + copied.setStartDate(interval.getStart()); + copied.setEndDate(interval.getEnd()); + assertTrue(copied.goesBeforeWithoutOverlapping(posterior)); + } + + } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java index b4a624200..31635faf3 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/IWorkerModel.java @@ -37,7 +37,15 @@ public interface IWorkerModel { Map, Collection> getLaboralRelatedCriterions(); - Set getLaboralRelatedCriterionSatisfactions( + List getLaboralRelatedCriterionSatisfactions( Worker worker); + public enum AddingSatisfactionResult { + OK, SATISFACTION_WRONG, DONT_COMPLY_OVERLAPPING_RESTRICTIONS; + } + + AddingSatisfactionResult addSatisfaction(ICriterionType type, + CriterionSatisfaction originalSatisfaction, + CriterionSatisfaction edited); + } \ No newline at end of file diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java index 94bf88f2e..9cd0a6945 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkRelationshipsController.java @@ -3,9 +3,8 @@ package org.navalplanner.web.resources.worker; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; @@ -13,8 +12,11 @@ import org.navalplanner.business.resources.entities.Criterion; import org.navalplanner.business.resources.entities.CriterionSatisfaction; import org.navalplanner.business.resources.entities.CriterionWithItsType; import org.navalplanner.business.resources.entities.ICriterionType; -import org.navalplanner.business.resources.entities.Interval; import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.web.common.IMessagesForUser; +import org.navalplanner.web.common.Level; +import org.navalplanner.web.common.Util; +import org.navalplanner.web.resources.worker.IWorkerModel.AddingSatisfactionResult; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Listbox; @@ -34,26 +36,28 @@ public class WorkRelationshipsController extends GenericForwardComposer { * CriterionSatisfaction(); */ - private CriterionSatisfaction editRelationship = new CriterionSatisfaction(); + private CriterionSatisfaction satisfactionEdited = new CriterionSatisfaction(); - private Collection workCriterions; + private List workCriterions; private Listbox selectedWorkCriterion; - /* - * private Datebox newWorkRelationshipStartDate; - * - * private Datebox newWorkRelationshipEndDate; - */ - private HashMap fromCriterionToType; private boolean editing; + private Component containerComponent; + + private CriterionSatisfaction originalSatisfaction; + + private final IMessagesForUser messagesForUser; + public WorkRelationshipsController(IWorkerModel workerModel, - WorkerCRUDController workerCRUDController) { + WorkerCRUDController workerCRUDController, + IMessagesForUser messagesForUser) { this.workerModel = workerModel; this.workerCRUDController = workerCRUDController; + this.messagesForUser = messagesForUser; this.workCriterions = new ArrayList(); Map, Collection> map = workerModel .getLaboralRelatedCriterions(); @@ -68,13 +72,12 @@ public class WorkRelationshipsController extends GenericForwardComposer { } } - public Set getCriterionSatisfactions() { - if (this.workerCRUDController.getWorker() == null) { - return new HashSet(); + public List getCriterionSatisfactions() { + if (getWorker() == null) { + return new ArrayList(); } else { return workerModel - .getLaboralRelatedCriterionSatisfactions(this.workerCRUDController - .getWorker()); + .getLaboralRelatedCriterionSatisfactions(getWorker()); } } @@ -86,47 +89,88 @@ public class WorkRelationshipsController extends GenericForwardComposer { } public void prepareForCreate() { - this.editRelationship = new CriterionSatisfaction(); + this.satisfactionEdited = new CriterionSatisfaction(); + this.originalSatisfaction = this.satisfactionEdited; + Util.reloadBindings(containerComponent); editing = false; } public void prepareForEdit(CriterionSatisfaction criterionSatisfaction) { - this.editRelationship = criterionSatisfaction; + this.satisfactionEdited = criterionSatisfaction.copy(); + this.originalSatisfaction = criterionSatisfaction; + Util.reloadBindings(containerComponent); + this.satisfactionEdited.setCriterion(select(this.satisfactionEdited + .getCriterion())); + // the criterion retrieved is used instead of the original one, so the + // call fromCriterionToType.get(criterion) works editing = true; } - public void saveCriterionSatisfaction() throws InstanceNotFoundException { - - // Add new criterion - Criterion selectedCriterion = (Criterion) selectedWorkCriterion - .getSelectedItem().getValue(); - CriterionWithItsType criterionWithItsType = fromCriterionToType - .get(selectedCriterion); - System.out.println("SAVE!!: " + selectedCriterion.getName()); - if (this.workerCRUDController.getWorker().contains(editRelationship)) { - this.workerCRUDController.getWorker().removeCriterionSatisfaction( - editRelationship); + private Criterion select(Criterion criterion) { + int i = 0; + for (Criterion c : workCriterions) { + if (c.isEquivalent(criterion)) { + selectedWorkCriterion.setSelectedIndex(i); + return c; + } + i++; } - this.workerCRUDController.getWorker().addSatisfaction( - criterionWithItsType, - Interval.range(editRelationship.getStartDate(), - editRelationship.getEndDate())); + throw new RuntimeException("not found criterion" + criterion); + } - // Delete the former one - workerCRUDController.getWorker().removeCriterionSatisfaction( - this.editRelationship); + public void saveCriterionSatisfaction() { + Criterion choosenCriterion = getChoosenCriterion(); + CriterionWithItsType criterionWithItsType = fromCriterionToType + .get(choosenCriterion); + satisfactionEdited.setCriterion(choosenCriterion); + AddingSatisfactionResult addSatisfaction = workerModel.addSatisfaction( + criterionWithItsType.getType(), originalSatisfaction, + satisfactionEdited); + switch (addSatisfaction) { + case OK: + messagesForUser.showMessage(Level.INFO, "Periodo gardado"); + this.workerCRUDController.goToEditForm(); + break; + case SATISFACTION_WRONG: + messagesForUser + .showMessage(Level.WARNING, + "O periodo ten datos inválidos. A fecha de fin debe ser posterior á de inicio"); + break; + case DONT_COMPLY_OVERLAPPING_RESTRICTIONS: + messagesForUser + .showMessage(Level.WARNING, + "O periodo non se puido gardar. Solápase cun periodo non compatible."); + this.workerCRUDController.goToEditForm(); + break; + default: + throw new RuntimeException("unexpected: " + addSatisfaction); + } + } - this.workerCRUDController.goToEditForm(); + private Criterion getChoosenCriterion() { + Criterion criterion; + if (editing) { + criterion = satisfactionEdited.getCriterion(); + } else { + criterion = (Criterion) this.selectedWorkCriterion + .getSelectedItemApi().getValue(); + } + return criterion; + } + + private Worker getWorker() { + return this.workerModel.getWorker(); } @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); + this.containerComponent = comp; this.selectedWorkCriterion.setSelectedIndex(0); } public CriterionSatisfaction getEditRelationship() { - return this.editRelationship; + return this.satisfactionEdited; } public Collection getWorkCriterions() { diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java index cafd97c5c..8edfb4091 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerCRUDController.java @@ -130,7 +130,6 @@ public class WorkerCRUDController extends GenericForwardComposer implements public void goToAddWorkRelationshipForm() { this.addWorkRelationship.prepareForCreate(); getVisibility().showOnly(addWorkRelationshipWindow); - Util.reloadBindings(addWorkRelationshipWindow); } public void goToCreateForm() { @@ -142,7 +141,6 @@ public class WorkerCRUDController extends GenericForwardComposer implements public void goToEditWorkRelationshipForm(CriterionSatisfaction satisfaction) { this.editWorkRelationship.prepareForEdit(satisfaction); getVisibility().showOnly(editWorkRelationshipWindow); - Util.reloadBindings(editWorkRelationshipWindow); } @Override @@ -158,12 +156,13 @@ public class WorkerCRUDController extends GenericForwardComposer implements throw new RuntimeException("messagesContainer is needed"); messages = new MessagesForUser(messagesContainer); this.addWorkRelationship = new WorkRelationshipsController( - this.workerModel, this); + this.workerModel, this, messages); setupWorkRelationshipController(this.addWorkRelationship, this.addWorkRelationshipWindow); setupWorkRelationshipController( this.editWorkRelationship = new WorkRelationshipsController( - this.workerModel, this), editWorkRelationshipWindow); + this.workerModel, this, messages), + editWorkRelationshipWindow); URLHandler handler = URLHandlerRegistry .getRedirectorFor(IWorkerCRUDControllerEntryPoints.class); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java index 15b2565a3..d33ea3b91 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/resources/worker/WorkerModel.java @@ -110,6 +110,34 @@ public class WorkerModel implements IWorkerModel { return worker.getAllSatisfactions(); } + @Transactional + public AddingSatisfactionResult addSatisfaction(ICriterionType type, + CriterionSatisfaction original, CriterionSatisfaction edited) { + Worker worker = getWorker(); + edited.setResource(worker); + boolean previouslyContained = false; + if (previouslyContained = worker.contains(original)) { + worker.removeCriterionSatisfaction(original); + } + boolean canAdd = false; + try { + canAdd = worker.canAddSatisfaction(type, edited); + } catch (IllegalArgumentException e) { + if (previouslyContained) { + worker.addSatisfaction(type, original); + } + return AddingSatisfactionResult.SATISFACTION_WRONG; + } + if (!canAdd) { + if (previouslyContained) { + worker.addSatisfaction(type, original); + } + return AddingSatisfactionResult.DONT_COMPLY_OVERLAPPING_RESTRICTIONS; + } + worker.addSatisfaction(type, edited); + return AddingSatisfactionResult.OK; + } + private static class NullAssigner implements IMultipleCriterionActiveAssigner { @@ -252,7 +280,8 @@ public class WorkerModel implements IWorkerModel { public void applyChanges() { for (CriterionSatisfaction criterionSatisfaction : added) { resource.addSatisfaction(new CriterionWithItsType(type, - criterionSatisfaction.getCriterion()), Interval.from(criterionSatisfaction.getStartDate())); + criterionSatisfaction.getCriterion()), Interval + .from(criterionSatisfaction.getStartDate())); } for (Criterion criterion : unassigned.keySet()) { resource.finish(new CriterionWithItsType(type, criterion)); @@ -307,13 +336,13 @@ public class WorkerModel implements IWorkerModel { } @Override - public Set getLaboralRelatedCriterionSatisfactions( + public List getLaboralRelatedCriterionSatisfactions( Worker worker) { Set result = new HashSet(); for (ICriterionType criterionType : laboralRelatedTypes) { result.addAll(worker.getSatisfactionsFor(criterionType)); } - return result; - + return worker.query().oneOf(laboralRelatedTypes).sortByStartDate() + .result(); } } \ No newline at end of file