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)
This commit is contained in:
Diego Pino Garcia 2010-05-26 16:29:38 +02:00 committed by Javier Moran Rua
parent 80d898c879
commit f9486de643
5 changed files with 379 additions and 13 deletions

View file

@ -83,4 +83,11 @@ public class DateAndHour implements Comparable<DateAndHour> {
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);
}
}

View file

@ -157,10 +157,19 @@ public class LimitingResourceAllocator {
}
private static Integer moveUntil(List<LimitingResourceQueueElement> 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;

View file

@ -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<LimitingResourceQueueElementGap, DateAndHour> endAllocationDates = new HashMap<LimitingResourceQueueElementGap, DateAndHour>();
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<LimitingResourceQueueElementGap> 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<LimitingResourceQueueElementGap, DateAndHour> calculateEndAllocationDates(
ResourceAllocation<?> resourceAllocation, Resource resource,
List<LimitingResourceQueueElementGap> gaps) {
Map<LimitingResourceQueueElementGap, DateAndHour> result = new HashMap<LimitingResourceQueueElementGap, DateAndHour>();
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\"}";
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}

View file

@ -22,6 +22,8 @@
<zk xmlns:n="http://www.zkoss.org/2005/zk/native">
<n:script type="text/javascript" src="/navalplanner-webapp/js/highlightDaysInInterval.js" />
<zscript><![CDATA[
limitingResourcesPanel = self;
]]>
@ -132,7 +134,8 @@
<panelchildren>
<listbox id="listCandidateGaps"
itemRenderer="@{limitingResourcesController.candidateGapRenderer}"
multiple="false">
multiple="false"
onSelect="limitingResourcesController.setStartAllocationDate(event)">
<listhead>
<listheader label="${i18n:_('Start')}" />
<listheader label="${i18n:_('End')}" />
@ -146,10 +149,14 @@
<vbox>
<panel title="${i18n:_('Select date')}" border="normal">
<panelchildren>
<radiogroup id="radioAllocationDate" orient="vertical">
<radiogroup id="radioAllocationDate" orient="vertical"
onCheck="limitingResourcesController.selectRadioAllocationDate(event)" >
<radio label="${i18n:_('Earliest date')}" selected="true" />
<radio label="${i18n:_('Latest date')}" />
<radio label="${i18n:_('Select start date')}" />
</radiogroup>
<datebox id="startAllocationDate"
onFocus="limitingResourcesController.highlightCalendar(event)" />
</panelchildren>
</panel>
</vbox>