From f9486de643d884946dd309b6970dc28e10260eb8 Mon Sep 17 00:00:00 2001 From: Diego Pino Garcia Date: Wed, 26 May 2010 16:29:38 +0200 Subject: [PATCH] ItEr58S10CUAsignacionRecursosLimitantesItEr57S11: Select day in calendar as the starting day where to allocate a queue element into a queue * Highlight days in datebox calendar * Fix some other bugs (cancel button, calculating gaps) --- .../planner/entities/DateAndHour.java | 7 + .../entities/LimitingResourceAllocator.java | 17 +- .../LimitingResourcesController.java | 154 ++++++++++++- .../main/webapp/js/highlightDaysInInterval.js | 203 ++++++++++++++++++ .../limitingResourcesLayout.zul | 11 +- 5 files changed, 379 insertions(+), 13 deletions(-) create mode 100644 navalplanner-webapp/src/main/webapp/js/highlightDaysInInterval.js diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DateAndHour.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DateAndHour.java index 0c7419a42..df1441530 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DateAndHour.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/DateAndHour.java @@ -83,4 +83,11 @@ public class DateAndHour implements Comparable { return (this.compareTo(dateAndHour) < 0); } + public boolean isAfter(DateAndHour dateAndHour) { + return (this.compareTo(dateAndHour) > 0); + } + + public boolean isEquals(DateAndHour dateAndHour) { + return (this.compareTo(dateAndHour) == 0); + } } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/LimitingResourceAllocator.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/LimitingResourceAllocator.java index e1635550f..0d9e46d3d 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/LimitingResourceAllocator.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/entities/LimitingResourceAllocator.java @@ -157,10 +157,19 @@ public class LimitingResourceAllocator { } private static Integer moveUntil(List elements, DateAndHour until) { - for (int pos = 0; pos < elements.size(); pos++) { - final LimitingResourceQueueElement each = elements.get(pos); - if (!until.isBefore(each.getStartTime())) { - return pos; + if (elements.size() > 0) { + // Space between until and first element start time + LimitingResourceQueueElement first = elements.get(0); + if (until.isBefore(first.getStartTime())) { + return 0; + } + + for (int pos = 0; pos < elements.size(); pos++) { + final LimitingResourceQueueElement each = elements.get(pos); + final DateAndHour startTime = each.getStartTime(); + if (until.isAfter(startTime) || until.isEquals(startTime)) { + return pos; + } } } return null; diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java index 0d612c2b8..13627a942 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourcesController.java @@ -26,7 +26,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; @@ -34,7 +36,6 @@ import org.apache.commons.lang.Validate; import org.joda.time.LocalDate; import org.navalplanner.business.orders.entities.Order; import org.navalplanner.business.planner.entities.DateAndHour; -import org.navalplanner.business.planner.entities.DayAssignment; import org.navalplanner.business.planner.entities.GenericResourceAllocation; import org.navalplanner.business.planner.entities.LimitingResourceAllocator; import org.navalplanner.business.planner.entities.LimitingResourceQueueElement; @@ -57,13 +58,16 @@ import org.zkoss.ganttz.timetracker.TimeTracker; import org.zkoss.ganttz.timetracker.zoom.SeveralModificators; import org.zkoss.ganttz.timetracker.zoom.ZoomLevel; import org.zkoss.zk.ui.SuspendNotAllowedException; +import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.SelectEvent; +import org.zkoss.zk.ui.util.Clients; import org.zkoss.zk.ui.util.Composer; import org.zkoss.zul.Button; import org.zkoss.zul.Checkbox; +import org.zkoss.zul.Datebox; import org.zkoss.zul.Grid; import org.zkoss.zul.Hbox; import org.zkoss.zul.Label; @@ -106,6 +110,10 @@ public class LimitingResourcesController implements Composer { private Radiogroup radioAllocationDate; + private Datebox startAllocationDate; + + private Map endAllocationDates = new HashMap(); + private final LimitingResourceQueueElementsRenderer limitingResourceQueueElementsRenderer = new LimitingResourceQueueElementsRenderer(); @@ -160,12 +168,15 @@ public class LimitingResourcesController implements Composer { this.parent.getChildren().clear(); this.parent.appendChild(limitingResourcesPanel); limitingResourcesPanel.afterCompose(); + gridUnassignedLimitingResourceQueueElements = (Grid) limitingResourcesPanel .getFellowIfAny("gridUnassignedLimitingResourceQueueElements"); manualAllocationWindow = (Window) limitingResourcesPanel.getFellowIfAny("manualAllocationWindow"); listAssignableQueues = (Listbox) manualAllocationWindow.getFellowIfAny("listAssignableQueues"); listCandidateGaps = (Listbox) manualAllocationWindow.getFellowIfAny("listCandidateGaps"); radioAllocationDate = (Radiogroup) manualAllocationWindow.getFellowIfAny("radioAllocationDate"); + startAllocationDate = (Datebox) manualAllocationWindow.getFellowIfAny("startAllocationDate"); + addCommands(limitingResourcesPanel); } catch (IllegalArgumentException e) { try { @@ -474,13 +485,48 @@ public class LimitingResourcesController implements Composer { private void feedValidGapsSince(LimitingResourceQueueElement element, LimitingResourceQueue queue, DateAndHour since) { List gaps = LimitingResourceAllocator.getValidGapsForElementSince(element, queue, since); + endAllocationDates = calculateEndAllocationDates(element.getResourceAllocation(), queue.getResource(), gaps); listCandidateGaps.setModel(new SimpleListModel(gaps)); if (!gaps.isEmpty()) { listCandidateGaps.setSelectedIndex(0); + setStartAllocationDate(gaps.get(0).getStartTime()); + startAllocationDate.setDisabled(true); disable(radioAllocationDate, false); } else { disable(radioAllocationDate, true); } + radioAllocationDate.setSelectedIndex(0); + } + + private void setStartAllocationDate(DateAndHour time) { + final Date date = (time != null) ? toDate(time.getDate()) : null; + startAllocationDate.setValue(date); + } + + private Map calculateEndAllocationDates( + ResourceAllocation resourceAllocation, Resource resource, + List gaps) { + + Map result = new HashMap(); + for (LimitingResourceQueueElementGap each: gaps) { + result.put(each, calculateEndAllocationDate(resourceAllocation, resource, each)); + } + return result; + } + + private DateAndHour calculateEndAllocationDate( + ResourceAllocation resourceAllocation, Resource resource, + LimitingResourceQueueElementGap gap) { + + if (gap.getEndTime() != null) { + return LimitingResourceAllocator.startTimeToAllocateStartingFromEnd(resourceAllocation, resource, gap); + } + return null; + } + + public void selectRadioAllocationDate(Event event) { + Radiogroup radiogroup = (Radiogroup) event.getTarget().getParent(); + startAllocationDate.setDisabled(radiogroup.getSelectedIndex() != 2); } private void disable(Radiogroup radiogroup, boolean disabled) { @@ -572,13 +618,15 @@ public class LimitingResourcesController implements Composer { } public void accept(Event e) { - LimitingResourceQueueElement element = limitingResourceQueueModel - .getLimitingResourceQueueElement(); - LimitingResourceQueue queue = getSelectedQueue(); - DateAndHour time = getSelectedAllocationTime(); + final LimitingResourceQueue queue = getSelectedQueue(); + final DateAndHour time = getSelectedAllocationTime(); + Validate.notNull(time); limitingResourceQueueModel .assignEditingLimitingResourceQueueElementToQueueAt(queue, time); Util.reloadBindings(gridUnassignedLimitingResourceQueueElements); + + LimitingResourceQueueElement element = limitingResourceQueueModel + .getLimitingResourceQueueElement(); limitingResourcesPanel.appendQueueElementToQueue(element); closeManualAllocationWindow(e); } @@ -586,12 +634,21 @@ public class LimitingResourcesController implements Composer { private DateAndHour getSelectedAllocationTime() { final LimitingResourceQueueElementGap selectedGap = getSelectedGap(); int index = radioAllocationDate.getSelectedIndex(); + + // Earliest date if (index == 0) { return getEarliestTime(selectedGap); + // Latest date } else if (index == 1) { return getLatestTime(selectedGap); + // Select start date } else if (index == 2) { - + LocalDate selectedDay = new LocalDate(startAllocationDate.getValue()); + DateAndHour allocationTime = getValidDayInGap(selectedDay, getSelectedGap()); + if (allocationTime == null) { + throw new WrongValueException(startAllocationDate, _("Day is not valid within selected gap")); + } + return allocationTime; } return null; } @@ -617,8 +674,38 @@ public class LimitingResourcesController implements Composer { return null; } - public void cancel() { + /** + * Checks if date is a valid day within gap. A day is valid within a gap if + * it is included between gap.startTime and the last day from which is + * possible to start doing an allocation (endAllocationDate) + * + * If date is valid, returns DateAndHour in gap associated with that date + * + * @param date + * @param gap + * @return + */ + private DateAndHour getValidDayInGap(LocalDate date, LimitingResourceQueueElementGap gap) { + final DateAndHour endAllocationDate = endAllocationDates.get(gap); + final LocalDate start = gap.getStartTime().getDate(); + final LocalDate end = endAllocationDate != null ? endAllocationDate.getDate() : null; + if (start.equals(date)) { + return gap.getStartTime(); + } + if (end != null && end.equals(date)) { + return endAllocationDate; + } + if ((start.compareTo(date) <= 0 + && (end == null || end.compareTo(date) >= 0))) { + return new DateAndHour(date, 0); + } + + return null; + } + + public void cancel() { + manualAllocationWindow.setVisible(false); } public void closeManualAllocationWindow(Event e) { @@ -626,4 +713,57 @@ public class LimitingResourcesController implements Composer { e.stopPropagation(); } + public void setStartAllocationDate(Event event) { + setStartAllocationDate(getSelectedGap().getStartTime()); + } + + public void highlightCalendar(Event event) { + Datebox datebox = (Datebox) event.getTarget(); + if (datebox.getValue() == null) { + final LocalDate startDate = getSelectedGap().getStartTime().getDate(); + datebox.setValue(toDate(startDate)); + } + highlightDaysInGap(datebox.getUuid(), getSelectedGap()); + } + + private Date toDate(LocalDate date) { + return date.toDateTimeAtStartOfDay().toDate(); + } + + public void highlightDaysInGap(String uuid, LimitingResourceQueueElementGap gap) { + final LocalDate start = gap.getStartTime().getDate(); + final LocalDate end = getEndAllocationDate(gap); + + final String jsCall = "highlightDaysInInterval('" + + uuid + "', '" + + jsonInterval(formatDate(start), formatDate(end)) + "', '" + + jsonHighlightColor() + "');"; + Clients.evalJavaScript(jsCall); + } + + private LocalDate getEndAllocationDate(LimitingResourceQueueElementGap gap) { + final DateAndHour endTime = endAllocationDates.get(gap); + return endTime != null ? endTime.getDate() : null; + } + + public String formatDate(LocalDate date) { + return (date != null) ? date.toString() : null; + } + + private String jsonInterval(String start, String end) { + StringBuilder result = new StringBuilder(); + + result.append("{\"start\": \"" + start + "\", "); + if (end != null) { + result.append("\"end\": \"" + end + "\""); + } + result.append("}"); + + return result.toString(); + } + + private String jsonHighlightColor() { + return "{\"color\": \"blue\", \"bgcolor\": \"white\"}"; + } + } diff --git a/navalplanner-webapp/src/main/webapp/js/highlightDaysInInterval.js b/navalplanner-webapp/src/main/webapp/js/highlightDaysInInterval.js new file mode 100644 index 000000000..b4b4eeda8 --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/js/highlightDaysInInterval.js @@ -0,0 +1,203 @@ +/* + This file is part of NavalPlan + + Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + Desenvolvemento Tecnolóxico de Galicia + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +var months = {"Jan": 0, "Feb": 1, "Mar": 2, "Apr": 3, "May": 4, "Jun": 5, "Jul": 6, "Aug": 7, "Sep": 8, "Oct": 9, "Nov": 10, "Dec": 11}; + +var DEFAULT_COLOR_STYLE = {"color": "blue", "bgcolor": "white"}; + +Array.prototype.in_array = function(p_val) { + for(var i = 0, l = this.length; i < l; i++) { + if(this[i] == p_val) { + return true; + } + } + return false; +} + +Date.prototype.compareTo = function(other) { + var this_milli = this.getTime(); + var other_milli = other.getTime(); + return this_milli - other_milli; +} + +Date.prototype.lesserThan = function(other) { + return this.compareTo(other) < 0; +} + +Date.prototype.greaterThan = function(other) { + return this.compareTo(other) > 0; +} + +Date.prototype.equals = function(other) { + return this.compareTo(other) == 0; +} + +Date.prototype.getDaysInMonth = function() { + return 32 - new Date(this.getFullYear(), this.getMonth(), 32).getDate(); +} + +String.prototype.trim = function(string) { + return this.replace("^\s+", "").replace("\s+$", ""); +} + +/** + * Returns number of month: 'Jan' => 0, 'Feb' => 1, etc + */ +function numberOfMonth(month) { + return months[month]; +} + +function dateAtBeginningOfMonth(monthAndYear) { + var arr = monthAndYear.split(","); + return new Date(arr[1].trim(), numberOfMonth(arr[0].trim()), 1); +} + +/** + * Parses date to Date(). Expects date in format ISO8601 (yyyy-MM-day) + */ +function toDate(date) { + if (date != undefined) { + var arr = date.split("-"); + + var year = arr[0]; + var month = arr[1] - 1; + var day = arr[2]; + + return new Date(year, month, day); + } + return null; +} + +/** + * Returns which days in date.month should be highlighted according to interval + * + * If interval is open, all days greater than interval.start should highlighted + * + */ +function daysToHighlightInInterval(interval, date) { + var start = toDate(interval.start); + var end = toDate(interval.end); + + if (sameMonthAndYear(start, date) + && sameMonthAndYear(start, end)) { + return daysDelta(start.getDate(), end.getDate()); + } + + if (sameMonthAndYear(start, date)) { + return daysDelta(start.getDate(), date.getDaysInMonth()); + } + + if (sameMonthAndYear(end, date)) { + return daysDelta(1, end.getDate()); + } + + if (start.lesserThan(date) && (end == null || end.greaterThan(date)) ) { + return daysDelta(1, date.getDaysInMonth()); + } + + return new Array(); +} + +function sameMonthAndYear(d1, d2) { + return (d1 != null && d2 != null + && d1.getFullYear() == d2.getFullYear() + && d1.getMonth() == d2.getMonth()); +} + +/** + * Returns an array of days from start to end (both included) + * + **/ +function daysDelta(start, end) { + var result = new Array(); + for (var i = start; i <= end; i++) { + result.push(i); + } + return result; +} + +/** + * Highlights elements in days array, turns off those days that are not in days + * + **/ +function setStyleForDays(nodes, days, colors) { + for (var i = 0; i < nodes.length; i++) { + var month = nodes[i].getAttribute("zk_monofs"); + if (month == null) { + continue; + } + + if (month == 0) { + var day = nodes[i].getAttribute("zk_day"); + + if (days.in_array(day)) { + nodes[i].setAttribute("style", colorStyleObj(colors)); + } else { + nodes[i].removeAttribute("style"); + } + } else { + nodes[i].setAttribute("style", colorStyle('lightgrey', 'white')); + } + } +} + +function colorStyleObj(obj) { + return colorStyle(obj.color, obj.bgcolor, obj.bold); +} + +function colorStyle(color, bgcolor, bold) { + var cssStyle = "color: " + color + "; background-color: " + bgcolor; + if (bold != undefined && bold) { + cssStyle += "; font-weight: bold"; + } + return cssStyle; +} + +/** + * Highlights those days in a calendar ZUL object that are within interval + * + * An interval is an object with two attributes: + * interval.start: date (year/month/day) + * interval.end: date (year/month/day) + * + * colorStyle is an object with two attributes: + * color.color: foreground color + * color.bgcolor: background color + * + * colorStyle is the color used to highlight a day (by default blue over white background) + * + */ +function highlightDaysInInterval(calendarUuid, intervalJSON, colorStyleJSON) { + + var calendar = document.getElementById(calendarUuid + "!pp"); + if (calendar == null) { + return; + } + var nodes = calendar.getElementsByTagName("td"); + var title = document.getElementById(calendarUuid + "!title"); // month and year: Jan, 1 + + var interval = eval("(" + intervalJSON + ")"); + var colorStyle = (colorStyleJSON != undefined) ? eval("(" + colorStyleJSON + ")") : DEFAULT_COLOR_STYLE; + + var currentDate = dateAtBeginningOfMonth(title.innerHTML); + var days = daysToHighlightInInterval(interval, currentDate); + + setStyleForDays(nodes, days, colorStyle); +} diff --git a/navalplanner-webapp/src/main/webapp/limitingresources/limitingResourcesLayout.zul b/navalplanner-webapp/src/main/webapp/limitingresources/limitingResourcesLayout.zul index 58a78d247..c8c9a0316 100644 --- a/navalplanner-webapp/src/main/webapp/limitingresources/limitingResourcesLayout.zul +++ b/navalplanner-webapp/src/main/webapp/limitingresources/limitingResourcesLayout.zul @@ -22,6 +22,8 @@ + + @@ -132,7 +134,8 @@ + multiple="false" + onSelect="limitingResourcesController.setStartAllocationDate(event)"> @@ -146,10 +149,14 @@ - + + +