Commit ed0b1f42 authored by Kurt Maute's avatar Kurt Maute

Add support for FF and SF relationships

parent 88d2775e
2006-04-19 Kurt Maute <Kurt@Maute.us>
* docs/libplanner/tmpl/mrp-relation.sgml
* libplanner/mrp-private.h
* libplanner/mrp-task-manager.c
* libplanner/mrp-task.c
* src/planner-task-dialog.c
Add support for FF and SF relationships
2006-04-18 Kjartan Maraas <kmaraas@gnome.org>
* configure.in: Remove obsolete entry for no_NO
* po/no.po: And the translation.
2006-04-11 Kurt Maute <Kurt@Maute.us>
* src/planner-usage-chart.c
Comment out 'View for Usage configured' message
2006-04-11 Alvaro del Castillo <acs@barrapunto.com>
* src/planner-usage-row.c
Disable right click edit dialogs. Enable edit dialogs with
double left click.
2006-04-11 Kurt Maute <Kurt@Maute.us>
* libplanner/mrp-task.h
* libplanner/mrp-task.c
* src/planner-task-tree.c
* configure.in
* src/planner-gantt-chart.c
* autogen.sh
* data/ui/gantt-view.ui
* libplanner/mrp-task-manager.c
* src/planner-task-tree.h
* src/planner-gantt-chart.h
* libplanner/mrp-calendar.c
* libplanner/mrp-calendar.h
* src/planner-gantt-view.c
* src/planner-task-view.c
* src/planner-gantt-row.c
* data/planner.schemas.in
Matteo Nastasi's patch to add:
* Gantt chart visualization of nonstandard working times
* Simple priority scheduling (previously known as vampire tasks)
2006-04-07 Alvaro del Castillo <acs@barrapunto.com>
* src/planner-usage-chart.c
......
......@@ -7,8 +7,7 @@ represents a task predecessor relation in the project.
<!-- ##### SECTION Long_Description ##### -->
<para>
A predecessor relation is used to affect the scheduling of a task
relative another task. The only type supported yet is "Finish to Start"
(%MRP_RELATION_FS). A relation may have a lag time associated to it,
relative another task. A relation may have a lag time associated to it,
so that a task can be scheduled to start after another task has
finished, plus a lag time.
</para>
......@@ -38,7 +37,7 @@ Object representing a predecessor relation between two tasks.
<!-- ##### ENUM MrpRelationType ##### -->
<para>
The type of relation. Only finish to start is implemented yet.
The type of relation.
</para>
@MRP_RELATION_NONE: invalid relation type (unset)
......
......@@ -89,8 +89,8 @@ void imrp_task_set_duration (MrpTask *task,
void imrp_task_set_work (MrpTask *task,
gint work);
MrpTaskGraphNode *imrp_task_get_graph_node (MrpTask *task);
MrpConstraint impr_task_get_constraint (MrpTask *task);
void impr_task_set_constraint (MrpTask *task,
MrpConstraint imrp_task_get_constraint (MrpTask *task);
void imrp_task_set_constraint (MrpTask *task,
MrpConstraint constraint);
gint imrp_task_get_depth (MrpTask *task);
GNode * imrp_task_get_node (MrpTask *task);
......
......@@ -121,6 +121,12 @@ task_manager_dump_task_tree (GNode *node);
static GObjectClass *parent_class;
static mrptime
task_manager_calculate_task_start_from_finish (MrpTaskManager *manager,
MrpTask *task,
mrptime finish,
gint *duration);
GType
mrp_task_manager_get_type (void)
......@@ -1111,78 +1117,23 @@ task_manager_build_dependency_graph (MrpTaskManager *manager)
manager->priv->needs_recalc = TRUE;
}
/* Calcluate the earliest start time that a particular predesessor relation
* allows given.
*/
static mrptime
task_manager_calc_relation (MrpTask *task,
MrpRelation *relation,
MrpTask *predecessor)
{
MrpRelationType type;
mrptime time;
/*mrptime start, finish;*/
/* FIXME: This does not work correctly for FF and SF. The problem is
* that the start and finish times of task is not known at this stage,
* so we can't really use them.
*/
type = mrp_relation_get_relation_type (relation);
switch (type) {
#if 0
case MRP_RELATION_FF:
/* finish-to-finish */
start = mrp_task_get_start (task);
finish = mrp_task_get_finish (task);
time = mrp_task_get_finish (predecessor) +
mrp_relation_get_lag (relation) - (finish - start);
break;
case MRP_RELATION_SF:
/* start-to-finish */
start = mrp_task_get_start (task);
finish = mrp_task_get_finish (task);
time = mrp_task_get_start (predecessor) +
mrp_relation_get_lag (relation) - (finish - start);
break;
#endif
case MRP_RELATION_SS:
/* start-to-start */
time = mrp_task_get_start (predecessor) +
mrp_relation_get_lag (relation);
break;
case MRP_RELATION_FS:
case MRP_RELATION_NONE:
default:
/* finish-to-start */
time = mrp_task_get_finish (predecessor) +
mrp_relation_get_lag (relation);
break;
}
return time;
}
/* Calculate the start time of the task by finding the latest finish of it's
* predecessors (plus any lag). Also take constraints into consideration.
*/
static mrptime
task_manager_calculate_task_start (MrpTaskManager *manager,
MrpTask *task)
MrpTask *task,
gint *duration)
{
MrpTaskManagerPriv *priv;
MrpTask *tmp_task;
GList *predecessors, *l;
MrpRelation *relation;
MrpRelationType type;
MrpTask *predecessor;
mrptime project_start;
mrptime start;
mrptime finish;
mrptime dep_start;
MrpConstraint constraint;
......@@ -1198,10 +1149,51 @@ task_manager_calculate_task_start (MrpTaskManager *manager,
relation = l->data;
predecessor = mrp_relation_get_predecessor (relation);
dep_start = task_manager_calc_relation (task,
relation,
predecessor);
type = mrp_relation_get_relation_type (relation);
switch (type) {
case MRP_RELATION_FF:
/* finish-to-finish */
/* predecessor must finish before successor can finish */
finish = mrp_task_get_finish (predecessor) + mrp_relation_get_lag (relation);
start = task_manager_calculate_task_start_from_finish (manager,
task,
finish,
duration);
dep_start = start;
break;
case MRP_RELATION_SF:
/* start-to-finish */
/* predecessor must start before successor can finish */
finish = mrp_task_get_start (predecessor);
start = task_manager_calculate_task_start_from_finish (manager,
task,
finish,
duration);
dep_start = mrp_task_get_start (predecessor) +
mrp_relation_get_lag (relation) - (finish - start);
break;
case MRP_RELATION_SS:
/* start-to-start */
/* predecessor must start before successor can start */
dep_start = mrp_task_get_start (predecessor) +
mrp_relation_get_lag (relation);
break;
case MRP_RELATION_FS:
case MRP_RELATION_NONE:
default:
/* finish-to-start */
/* predecessor must finish before successor can start */
dep_start = mrp_task_get_finish (predecessor) +
mrp_relation_get_lag (relation);
break;
}
start = MAX (start, dep_start);
}
......@@ -1209,7 +1201,7 @@ task_manager_calculate_task_start (MrpTaskManager *manager,
}
/* Take constraint types in consideration. */
constraint = impr_task_get_constraint (task);
constraint = imrp_task_get_constraint (task);
switch (constraint.type) {
case MRP_CONSTRAINT_SNET:
/* Start-no-earlier-than. */
......@@ -1843,6 +1835,149 @@ task_manager_calculate_task_finish (MrpTaskManager *manager,
return finish;
}
/* Calculate the start time from the work needed for the task, and the effort
* that the allocated resources add to the task. Uses the project calendar if no
* resources are allocated. This function also sets the work_start property of
* the task, which is the first time that actually has work scheduled, this can
* differ from the start if start is inside a non-work period.
*/
static mrptime
task_manager_calculate_task_start_from_finish (MrpTaskManager *manager,
MrpTask *task,
mrptime finish,
gint *duration)
{
MrpTaskManagerPriv *priv;
mrptime start;
mrptime t;
mrptime t1, t2;
mrptime work_start;
mrptime project_start;
gint work;
gint effort;
gint delta;
GList *unit_ivals, *l;
MrpUnitsInterval *unit_ival;
MrpTaskType type;
MrpTaskSched sched;
priv = manager->priv;
if (task == priv->root) {
g_warning ("Tried to get duration of root task.");
return 0;
}
effort = 0;
start = finish;
work_start = -1;
t = mrp_time_align_day (start);
project_start = mrp_project_get_project_start (priv->project);
/* Milestone tasks can be special cased, no duration. */
type = mrp_task_get_task_type (task);
if (type == MRP_TASK_TYPE_MILESTONE) {
*duration = 0;
task_manager_calculate_milestone_work_start (manager, task, start);
return start;
}
work = mrp_task_get_work (task);
sched = mrp_task_get_sched (task);
if (sched == MRP_TASK_SCHED_FIXED_WORK) {
*duration = 0;
} else {
*duration = mrp_task_get_duration (task);
}
while (1) {
unit_ivals = g_list_reverse (task_manager_get_task_units_intervals (manager, task, t));
/* If we don't get anywhere in 100 days, then the calendar must
* be broken, so we abort the scheduling of this task. It's not
* the best solution but fixes the issue for now.
*/
if (effort == 0 && finish - t > (60*60*24*100)) {
break;
}
if (!unit_ivals) {
t -= 60*60*24;
continue;
}
for (l = unit_ivals; l; l = l->next) {
unit_ival = l->data;
t1 = t + unit_ival->start;
t2 = t + unit_ival->end;
/* Skip any intervals after the task ends. */
if (t1 > finish) {
continue;
}
/* Don't add time after the finish time of the task. */
t2 = MIN (t2, finish);
if (t1 == t2) {
continue;
}
if (work_start == -1) {
work_start = t1;
}
/* Effort added by this interval. */
if (sched == MRP_TASK_SCHED_FIXED_WORK) {
delta = floor (0.5 + (double) unit_ival->units * (t2 - t1) / 100.0);
*duration += (t2 - t1);
if (effort + delta >= work) {
start = t2 - floor (0.5 + (work - effort) / unit_ival->units * 100.0);
/* Subtract the spill. */
*duration -= floor (0.5 + (effort + delta - work) / unit_ival->units * 100.0);
goto done;
}
}
else if (sched == MRP_TASK_SCHED_FIXED_DURATION) {
delta = t2 - t1;
if (effort + delta >= *duration) {
/* Done, make sure we don't spill. */
start = t2 - (*duration - effort);
goto done;
}
} else {
/* Schedule is either fixed work of fixed duration - we should never get here */
delta = 0;
g_assert_not_reached ();
}
effort += delta;
}
t -= 60*60*24;
}
done:
start = MAX (start, project_start);
if (work_start == -1) {
work_start = start;
}
imrp_task_set_work_start (task, work_start);
g_list_foreach (unit_ivals, (GFunc) g_free, NULL);
g_list_free (unit_ivals);
return start;
}
static void
task_manager_do_forward_pass_helper (MrpTaskManager *manager,
MrpTask *task)
......@@ -1911,7 +2046,7 @@ task_manager_do_forward_pass_helper (MrpTaskManager *manager,
imrp_task_set_duration (task, duration);
} else {
/* Non-summary task. */
t1 = task_manager_calculate_task_start (manager, task);
t1 = task_manager_calculate_task_start (manager, task, &duration);
t2 = task_manager_calculate_task_finish (manager, task, t1, &duration);
imrp_task_set_start (task, t1);
......
......@@ -1051,9 +1051,13 @@ mrp_task_add_predecessor (MrpTask *task,
glong lag,
GError **error)
{
MrpRelation *relation;
MrpProject *project;
MrpTaskManager *manager;
MrpRelation *relation;
MrpProject *project;
MrpTaskManager *manager;
GList *relations;
gchar *tmp;
MrpConstraint constraint;
mrptime pred_start;
g_return_val_if_fail (MRP_IS_TASK (task), NULL);
g_return_val_if_fail (MRP_IS_TASK (predecessor), NULL);
......@@ -1068,7 +1072,60 @@ mrp_task_add_predecessor (MrpTask *task,
return NULL;
}
relations = mrp_task_get_predecessor_relations (task);
/* check for attempt to add SF or FF relation when other relation types already present */
if ((type == MRP_RELATION_SF || type == MRP_RELATION_FF) && relations) {
if (type == MRP_RELATION_SF) {
tmp = _("Start to Finish relation type cannot be combined with other relations.");
} else {
tmp = _("Finish to Finish relation type cannot be combined with other relations.");
}
g_set_error (error,
MRP_ERROR,
MRP_ERROR_TASK_RELATION_FAILED,
tmp);
return NULL;
}
/* check for attempt to add SF or FF when a Start No Earlier Than constraint exists */
constraint = imrp_task_get_constraint (task);
if ((type == MRP_RELATION_SF || type == MRP_RELATION_FF) &&
constraint.type == MRP_CONSTRAINT_SNET) {
if (type == MRP_RELATION_SF) {
tmp = _("Start to Finish relation type cannot be combined with Start No Earlier Than constraint.");
} else {
tmp = _("Finish to Finish relation type cannot be combined with Start No Earlier Than constraint.");
}
g_set_error (error,
MRP_ERROR,
MRP_ERROR_TASK_RELATION_FAILED,
tmp);
return NULL;
}
/* check for attempt to add SF when predecessor starts on project start date
this would of course cause the task to be scheduled before project start */
project = mrp_object_get_project (MRP_OBJECT (task));
pred_start = mrp_time_align_day (mrp_task_get_work_start (predecessor));
if ((type == MRP_RELATION_SF) &&
pred_start == mrp_project_get_project_start (project)) {
g_set_error (error,
MRP_ERROR,
MRP_ERROR_TASK_RELATION_FAILED,
_("Start to Finish relation cannot be set. Predecessor starts on project start date."));
return NULL;
}
manager = imrp_project_get_task_manager (project);
if (!mrp_task_manager_check_predecessor (manager, task, predecessor, error)) {
return NULL;
......@@ -1875,7 +1932,7 @@ imrp_task_set_latest_finish (MrpTask *task,
}
MrpConstraint
impr_task_get_constraint (MrpTask *task)
imrp_task_get_constraint (MrpTask *task)
{
MrpConstraint c = { 0 };
......@@ -1885,7 +1942,7 @@ impr_task_get_constraint (MrpTask *task)
}
void
impr_task_set_constraint (MrpTask *task, MrpConstraint constraint)
imrp_task_set_constraint (MrpTask *task, MrpConstraint constraint)
{
g_return_if_fail (MRP_IS_TASK (task));
......
......@@ -31,6 +31,7 @@
#include <gtk/gtk.h>
#include <libplanner/mrp-object.h>
#include <libplanner/mrp-project.h>
#include <libplanner/mrp-private.h>
#include "libplanner/mrp-paths.h"
#include "planner-cell-renderer-list.h"
#include "planner-assignment-model.h"
......@@ -1844,13 +1845,52 @@ static GtkWidget *
task_dialog_predecessor_dialog_new (MrpTask *task,
PlannerWindow *main_window)
{
MrpProject *project;
GladeXML *glade;
GtkWidget *dialog;
GtkWidget *w;
GList *tasks;
gchar *filename;
MrpProject *project;
GladeXML *glade;
GtkWidget *dialog;
GtkWidget *w;
GList *tasks;
gchar *filename;
GList *relations, *l;
MrpRelationType rel_type;
MrpConstraint constraint;
/* check for attempt to add relation when Must Start On constraint is present */
constraint = imrp_task_get_constraint (task);
if (constraint.type == MRP_CONSTRAINT_MSO) {
dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"You cannot add a relationship to a task with a Must Start On constraint.");
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return NULL;
}
/* check for attempt to add relation when SF or FF already present */
relations = mrp_task_get_predecessor_relations (task);
for (l = relations; l; l = l->next) {
rel_type = mrp_relation_get_relation_type (l->data);
if (rel_type == MRP_RELATION_SF || rel_type == MRP_RELATION_FF) {
dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"You cannot add a relationship if a Start to Finish or Finish to Finish relationship already exists.");
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return NULL;
}
}
mrp_object_get (task, "project", &project, NULL);
filename = mrp_paths_get_glade_dir ("add-predecessor.glade");
......@@ -1871,16 +1911,13 @@ task_dialog_predecessor_dialog_new (MrpTask *task,
w = glade_xml_get_widget (glade, "type_optionmenu");
g_object_set_data (G_OBJECT (dialog), "type_optionmenu", w);
/* FIXME: FF and SF are disabled for now, since the scheduler doesn't
* handle them.
*/
task_dialog_setup_option_menu (w,
NULL,
NULL,
_("Finish to start (FS)"), MRP_RELATION_FS,
/*_("Finish to finish (FF)"), MRP_RELATION_FF,*/
_("Finish to finish (FF)"), MRP_RELATION_FF,
_("Start to start (SS)"), MRP_RELATION_SS,
/*_("Start to finish (SF)"), MRP_RELATION_SF,*/
_("Start to finish (SF)"), MRP_RELATION_SF,
NULL);
w = glade_xml_get_widget (glade, "lag_entry");
......@@ -1973,7 +2010,9 @@ task_dialog_add_predecessor_cb (GtkWidget *widget,
GtkWidget *dialog;
dialog = task_dialog_predecessor_dialog_new (data->task, data->main_window);
gtk_widget_show (dialog);
if (dialog) {
gtk_widget_show (dialog);
}
}
static void
......@@ -2134,7 +2173,11 @@ cell_index_to_relation_type (gint i)
case 0:
return MRP_RELATION_FS;
case 1:
return MRP_RELATION_FF;
case 2:
return MRP_RELATION_SS;
case 3:
return MRP_RELATION_SF;
default:
g_warning ("Unknown relation type index");
return MRP_RELATION_FS;
......@@ -2170,13 +2213,11 @@ task_dialog_cell_type_show_popup (PlannerCellRendererList *cell,
relation = mrp_task_get_relation (data->task, predecessor);
/* FIXME: FF and SF are disabled for now. */
list = NULL;
list = g_list_append (list, g_strdup (_("FS")));
/*list = g_list_append (list, g_strdup (_("FF")));*/
list = g_list_append (list, g_strdup (_("FF")));
list = g_list_append (list, g_strdup (_("SS")));
/*list = g_list_append (list, g_strdup (_("SF")));*/
list = g_list_append (list, g_strdup (_("SF")));
cell->list = list;
......@@ -2184,18 +2225,15 @@ task_dialog_cell_type_show_popup (PlannerCellRendererList *cell,
case MRP_RELATION_FS:
cell->selected_index = 0;
break;
case MRP_RELATION_SS:
cell->selected_index = 1;
break;
#if 0
/* FIXME: FF and SF disabled. Renumber indices when enabling. */
case MRP_RELATION_FF:
cell->selected_index = 1;
break;
case MRP_RELATION_SS:
cell->selected_index = 2;
break;
case MRP_RELATION_SF:
cell->selected_index = 3;
break;
#endif
default:
cell->selected_index = 0;
break;
......@@ -2271,12 +2309,54 @@ task_dialog_schedule_popdown_cb (PlannerPopupButton *popup_button,
gboolean ok,
DialogData *data)
{
MrpConstraint constraint;
MrpConstraint constraint;
GList *relations, *l;
GtkWidget *dialog;
MrpRelationType rel_type;
if (ok) {
constraint.time = planner_task_date_widget_get_date (PLANNER_TASK_DATE_WIDGET (widget));
constraint.type = planner_task_date_widget_get_constraint_type (PLANNER_TASK_DATE_WIDGET (widget));
relations = mrp_task_get_predecessor_relations (data->task);
/* check for attempt to add MSO constraint when relations are present */
if (constraint.type == MRP_CONSTRAINT_MSO && relations) {
dialog = gtk_message_dialog_new (GTK_WINDOW (data->main_window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"You cannot add a Must Start On constraint with predecessor relations defined for this task.");
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
gtk_widget_destroy (widget);
return;
}
/* check for attempt to add MSO constraint when relations are present */
if (constraint.type == MRP_CONSTRAINT_SNET && relations) {
for (l = relations; l; l = l->next) {
rel_type = mrp_relation_get_relation_type (l->data);
if (rel_type == MRP_RELATION_SF || rel_type == MRP_RELATION_FF) {
dialog = gtk_message_dialog_new (GTK_WINDOW (data->main_window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"You cannot add a Start No Earlier Than constraint because a Start to Finish or Finish to Finish predecessor relationship exists for this task.");
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
gtk_widget_destroy (widget);
return;
}
}
}
task_cmd_edit_constraint (data->main_window,
data->task,
&constraint);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment