pax_global_header 0000666 0000000 0000000 00000000064 14412526641 0014517 g ustar 00root root 0000000 0000000 52 comment=306874456a323bbc95b16b90e0709952ac6e9ba2
intranet-gantt-editor-master/ 0000775 0000000 0000000 00000000000 14412526641 0016577 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/README.md 0000664 0000000 0000000 00000012466 14412526641 0020067 0 ustar 00root root 0000000 0000000 # ]po[ Gantt Editor
This package is part of ]project-open[, an open-source enterprise project management system.
For more information about ]project-open[ please see:
* [Documentation Wiki](https://www.project-open.com/en/)
* [V5.0 Download](https://sourceforge.net/projects/project-open/files/project-open/V5.0/)
* [Installation Instructions](https://www.project-open.com/en/list-installers)
About ]po[ Gantt Editor:
This package implements a project Gantt Editor in HTML5 ("AJAX") technology as a single-page open-source application similar to
import schedules from these toolsThe current development status is available as part of our V5.0.beta demo server . Please log in as "Ben Bigboss" or click on this URL for a direct link to an example project. Next choose sub tab "Gantt Editor":
# Online Reference Documentation
## Procedure Files
## Procedures
## SQL Files
## Content Pages
intranet-gantt-editor-master/catalog/ 0000775 0000000 0000000 00000000000 14412526641 0020211 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/catalog/intranet-gantt-editor.de_DE.ISO-8859-1.xml 0000664 0000000 0000000 00000000411 14412526641 0027313 0 ustar 00root root 0000000 0000000
REST - Project Member Assignments and Absences
intranet-gantt-editor-master/catalog/intranet-gantt-editor.en_US.ISO-8859-1.xml 0000664 0000000 0000000 00000000411 14412526641 0027364 0 ustar 00root root 0000000 0000000
REST - Project Member Assignments and Absences
intranet-gantt-editor-master/catalog/intranet-gantt-editor.nl_NL.ISO-8859-1.xml 0000664 0000000 0000000 00000000410 14412526641 0027354 0 ustar 00root root 0000000 0000000
REST - Projectlid toewijzingen en afwezigheid
intranet-gantt-editor-master/intranet-gantt-editor.info 0000664 0000000 0000000 00000007550 14412526641 0023706 0 ustar 00root root 0000000 0000000
]project-open[ Gantt Editor
]project-open[ Gantt Editor
f
t
f
f
intranet-gantt-editor
Frank Bergmann
Gantt editor
2016-11-15
]project-open[
]project-open[ Free License (FL)
Sencha implementation of a Gantt editor similar to ProjectLibre and MS-Project.
0
intranet-gantt-editor-master/lib/ 0000775 0000000 0000000 00000000000 14412526641 0017345 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/lib/gantt-editor.adp 0000664 0000000 0000000 00000060661 14412526641 0022445 0 ustar 00root root 0000000 0000000
Project #@project_id@ is a sub-project, so we can't show a Gantt Editor for it.
intranet-gantt-editor-master/lib/gantt-editor.tcl 0000664 0000000 0000000 00000011054 14412526641 0022453 0 ustar 00root root 0000000 0000000 # /packages/intranet-gantt-editor/lib/gantt-editor.tcl
#
# Copyright (C) 2012 ]project-open[
#
# All rights reserved. Please check
# https://www.project-open.com/license/ for details.
# ----------------------------------------------------------------------
#
# ---------------------------------------------------------------------
# The following variables are expected in the environment
# defined by the calling /tcl/*.tcl libary:
# project_id
set page_url [im_url_with_query]
set current_user_id [auth::require_login]
set main_project_id $project_id; # project_id may be overwritten by SQLs below
set main_project_parent_id [db_string mppi "select parent_id from im_projects where project_id = :main_project_id" -default ""]
if {"" ne $main_project_parent_id} { set main_project_id "" }
# Create a debug JSON object that controls logging verbosity
set debug_default "default 0"
set debug_list [parameter::get_from_package_key -package_key "intranet-gantt-editor" -parameter DebugHash -default $debug_default]
array set debug_hash $debug_list
set debug_json_list {}
foreach id [array names debug_hash] { lappend debug_json_list "'$id': $debug_hash($id)" }
set debug_json "{\n\t[join $debug_json_list ",\n\t"]\n}"
set baseline_p [im_table_exists im_baselines]
# Default value for cross-project overassignments.
# Showing this data can be very slow in certain organizations (same people assigned to all projects...)
set default_cross_project_overassignments [parameter::get_from_package_key -package_key "intranet-gantt-editor" -parameter "DefaultCrossProjectOverassignmentsVisibility" -default "true"]
# Determine the permission of the user
im_project_permissions $current_user_id $main_project_id view_p read_p write_p admin_p
# Create a random ID for the gantt editor
set gantt_editor_rand [expr {round(rand() * 100000000.0)}]
set gantt_editor_id "gantt_editor_$gantt_editor_rand"
# Limit the size of a project to 20 years, in order to avoid performance
# issues that can break the entire system...
set max_project_years [parameter::get_from_package_key -package_key "intranet-gantt-editor" -parameter MaxProjectYears -default "20"]
db_1row project_info "
select least(max(end_date), min(start_date + '$max_project_years years'::interval)) as report_end_date,
min(start_date) as report_start_date,
(select parent_id from im_projects where project_id = :project_id) as main_parent_id
from (
select sub_p.start_date,
sub_p.end_date
from im_projects sub_p,
im_projects main_p
where sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = :project_id
) t
"
if {"" eq $report_start_date} { set report_start_date [db_string now "select now()"] }
if {"" eq $report_end_date} { set report_end_date [db_string now "select now()"] }
# Default material and Unit of Measure: "Default" and "Hour"
set default_material_id [im_material_default_material_id]
set default_cost_center_id [im_cost_center_company]
set default_uom_id [im_uom_hour]
# 9722 = 'Fixed Work' is the default effort_driven_type
set default_effort_driven_type_id [parameter::get_from_package_key -package_key "intranet-ganttproject" -parameter "DefaultEffortDrivenTypeId" -default "9722"]
# ----------------------------------------------------------------
# Check that all tasks in the project have a reasonable start- and end-date.
#
set tasks_with_bad_start_end [db_list tasks_without_start_or_end "
select sub_p.project_id
from im_projects sub_p,
im_projects main_p
where main_p.project_id = :project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
(sub_p.start_date is null OR sub_p.end_date is null)
"]
foreach task_id $tasks_with_bad_start_end {
db_1row sub_info "
select parent_id,
start_date,
least(:report_start_date::date, coalesce(start_date, :report_start_date), coalesce(end_date, :report_end_date)) as least_date,
end_date,
greatest(:report_end_date, coalesce(start_date, :report_end_date), coalesce(end_date, :report_end_date)) as greatest_date
from im_projects
where project_id = :task_id
"
if {"" eq $start_date} {
db_dml update_start "update im_projects set start_date = :least_date where project_id = :task_id"
# Write Audit Trail
im_audit -object_id $task_id -comment "Fixed empty start_date with default start in Gantt Editor"
}
if {"" eq $end_date} {
db_dml update_end "update im_projects set end_date = :greatest_date where project_id = :task_id"
# Write Audit Trail
im_audit -object_id $task_id -comment "Fixed empty end_date with default end in Gantt Editor"
}
}
intranet-gantt-editor-master/lib/task-editor.adp 0000664 0000000 0000000 00000013711 14412526641 0022264 0 ustar 00root root 0000000 0000000
intranet-gantt-editor-master/lib/task-editor.tcl 0000664 0000000 0000000 00000001267 14412526641 0022305 0 ustar 00root root 0000000 0000000 # /packages/sencha-task-editor/lib/task-editor.tcl
#
# Copyright (C) 2012 ]project-open[
#
# All rights reserved. Please check
# https://www.project-open.com/license/ for details.
# ----------------------------------------------------------------------
#
# ---------------------------------------------------------------------
# The following variables are expected in the environment
# defined by the calling /tcl/*.tcl libary:
# project_id
set data_list {}
# project_id may be overwritten by SQLs below
set main_project_id $project_id
# Create a random ID for the task_editor
set task_editor_rand [expr {round(rand() * 100000000.0)}]
set task_editor_id "task_editor_$task_editor_rand"
intranet-gantt-editor-master/sql/ 0000775 0000000 0000000 00000000000 14412526641 0017376 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/sql/postgresql/ 0000775 0000000 0000000 00000000000 14412526641 0021601 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/sql/postgresql/intranet-gantt-editor-create.sql 0000664 0000000 0000000 00000017021 14412526641 0030007 0 ustar 00root root 0000000 0000000 -- /packages/intranet-gantt-editor/sql/postgresql/intranet-gantt-editor-create.sql
--
-- Copyright (c) 2010 ]project-open[
--
-- All rights reserved. Please check
-- https://www.project-open.com/license/ for details.
--
-- @author frank.bergmann@project-open.com
-- ------------------------------------------------------------
-- Gantt Editor Portlet
-- ------------------------------------------------------------
SELECT im_component_plugin__new (
null, -- plugin_id
'im_component_plugin', -- object_type
now(), -- creation_date
null, -- creation_user
null, -- creation_ip
null, -- context_id
'Gantt Editor', -- plugin_name
'intranet-gantt-editor', -- package_name
'top', -- location
'/intranet/projects/view', -- page_url
null, -- view_name
10, -- sort_order
'gantt_editor_portlet -project_id $project_id'
);
SELECT acs_permission__grant_permission(
(select plugin_id from im_component_plugins where plugin_name = 'Gantt Editor' and package_name = 'intranet-gantt-editor'),
(select group_id from groups where group_name = 'Employees'),
'read'
);
---------------------------------------------------------
-- REST Data-Sources
--
-- These reports are portfolio-planner specific, so we do
-- not have to add them to sencha-core.
---------------------------------------------------------
-- List all intra-project dependencies on the server
--
SELECT im_report_new (
'REST Intra-Project Task Dependencies', -- report_name
'rest_intra_project_task_dependencies', -- report_code
'intranet-gantt-editor', -- package_key
220, -- report_sort_order
(select menu_id from im_menus where label = 'reporting-rest'), -- parent_menu_id
''
);
update im_reports set
report_description = 'Returns the list of intra-project dependencies',
report_sql = '
select d.dependency_id as id,
d.*,
main_project.project_id as main_project_id_one,
main_project.project_name as main_project_name_one,
p_one.project_id as task_one_id,
p_one.project_name as task_one_name,
p_one.start_date as task_one_start_date,
p_one.end_date as task_one_end_date,
p_two.project_id as task_two_id,
p_two.project_name as task_two_name,
p_two.start_date as task_two_start_date,
p_two.end_date as task_two_end_date
from im_timesheet_task_dependencies d,
im_projects p_one,
im_projects p_two,
im_projects main_project
where main_project.project_id = %main_project_id% and
p_one.project_id = d.task_id_one and
p_two.project_id = d.task_id_two and
p_one.tree_sortkey betweeen main_project.tree_sortkey and tree_right(main_project.tree_sortkey) and
p_two.tree_sortkey betweeen main_project.tree_sortkey and tree_right(main_project.tree_sortkey)
order by p_one.tree_sortkey, p_two.tree_sortkey
'
where report_code = 'rest_intra_project_task_dependencies';
-- Relatively permissive
SELECT acs_permission__grant_permission(
(select menu_id from im_menus where label = 'rest_intra_project_task_dependencies'),
(select group_id from groups where group_name = 'Employees'),
'read'
);
-- Check for assignments of project members to other projects or for absences
--
SELECT im_report_new (
'REST Project Member Assignments and Absences', -- report_name
'rest_project_member_assignments_absences', -- report_code
'intranet-gantt-editor', -- package_key
230, -- report_sort_order
(select menu_id from im_menus where label = 'reporting-rest'), -- parent_menu_id
''
);
update im_reports set
report_description = 'Lists absences and assignments to other projects of the members of a project',
report_sql = '
-- Individual absences from im_user_absences (by user)
select t.*,
im_name_from_user_id(user_id) as user_name,
acs_object__name(context_id) as context
from (
select
a.absence_id as object_id,
''im_user_absence'' as object_type,
a.owner_id as user_id,
a.start_date,
a.end_date,
100 as percentage,
a.absence_name as name,
0 as context_id
from im_user_absences a
where a.owner_id in (
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
a.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
a.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
UNION
-- Group absences such as bank holidays
select
a.absence_id as object_id,
''im_user_absence'' as object_type,
gei.element_id as user_id,
a.start_date,
a.end_date,
100 as percentage,
a.absence_name as name,
0 as context_id
from im_user_absences a,
group_element_index gei
where a.group_id = gei.group_id and
gei.element_id in (
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
a.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
a.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
UNION
-- Assignments to other projects
select p.project_id as object_id,
''im_project'' as object_type,
pe.person_id as user_id,
p.start_date,
p.end_date,
coalesce(bom.percentage, 0) as percentage,
p.project_name as name,
(select main_p.project_id from im_projects main_p
where main_p.tree_sortkey = tree_root_key(p.tree_sortkey)
) as context_id
from persons pe,
im_projects p,
im_projects super_p,
acs_rels r,
im_biz_object_members bom
where
super_p.parent_id is null and
p.parent_id is not null and -- Ignore assignments on a main project level
p.tree_sortkey between super_p.tree_sortkey and tree_right(super_p.tree_sortkey) and
super_p.project_status_id in (select * from im_sub_categories(76)) and
p.project_status_id in (select * from im_sub_categories(76)) and
r.rel_id = bom.rel_id and
r.object_id_one = p.project_id and
r.object_id_two = pe.person_id and
pe.person_id in (
-- Only report about persons assigned in main_p
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and p.project_id not in (
-- Exclude assignments within main_p (handled in JS)
select sub_p.project_id
from im_projects sub_p,
im_projects main_p
where sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
p.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
p.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
) t
where percentage > 0.0
order by
object_type,
object_id,
user_id
'
where report_code = 'rest_project_member_assignments_absences';
-- Relatively permissive
SELECT acs_permission__grant_permission(
(select menu_id from im_menus where label = 'rest_project_member_assignments_absences'),
(select group_id from groups where group_name = 'Employees'),
'read'
);
intranet-gantt-editor-master/sql/postgresql/intranet-gantt-editor-drop.sql 0000664 0000000 0000000 00000000613 14412526641 0027507 0 ustar 00root root 0000000 0000000 -- /packages/intranet-gantt-editor/sql/postgresql/intranet-gantt-editor-drop.sql
--
-- Copyright (c) 2010 ]project-open[
--
-- All rights reserved. Please check
-- https://www.project-open.com/license/ for details.
--
-- @author frank.bergmann@project-open.com
select im_component_plugin__del_module('intranet-gantt-editor');
select im_menu__del_module('intranet-gantt-editor');
intranet-gantt-editor-master/sql/postgresql/upgrade/ 0000775 0000000 0000000 00000000000 14412526641 0023230 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/sql/postgresql/upgrade/upgrade-5.0.3.0.2-5.0.3.0.3.sql 0000664 0000000 0000000 00000000224 14412526641 0027275 0 ustar 00root root 0000000 0000000 -- 5.0.3.0.2-5.0.3.0.3.sql
SELECT acs_log__debug('/packages/intranet-gantt-editor/sql/postgresql/upgrade/upgrade-5.0.3.0.2-5.0.3.0.3.sql','');
intranet-gantt-editor-master/sql/postgresql/upgrade/upgrade-5.0.4.0.0-5.0.4.0.1.sql 0000664 0000000 0000000 00000010045 14412526641 0027275 0 ustar 00root root 0000000 0000000 -- upgrade-5.0.4.0.0-5.0.4.0.1.sql
SELECT acs_log__debug('/packages/intranet-gantt-editor/sql/postgresql/upgrade/upgrade-5.0.4.0.0-5.0.4.0.1.sql', '');
update im_reports set
report_description = 'Lists absences and assignments to other projects of the members of a project',
report_sql = '
-- Individual absences from im_user_absences (by user)
select t.*,
im_name_from_user_id(user_id) as user_name,
acs_object__name(context_id) as context
from (
select
a.absence_id as object_id,
''im_user_absence'' as object_type,
a.owner_id as user_id,
a.start_date,
a.end_date,
100 as percentage,
a.absence_name as name,
0 as context_id
from im_user_absences a
where a.owner_id in (
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
a.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
a.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
UNION
-- Group absences such as bank holidays
select
a.absence_id as object_id,
''im_user_absence'' as object_type,
gei.element_id as user_id,
a.start_date,
a.end_date,
100 as percentage,
a.absence_name as name,
0 as context_id
from im_user_absences a,
group_element_index gei
where a.group_id = gei.group_id and
gei.element_id in (
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
a.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
a.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
UNION
-- Assignments to other projects
select p.project_id as object_id,
''im_project'' as object_type,
pe.person_id as user_id,
p.start_date,
p.end_date,
coalesce(bom.percentage, 0) as percentage,
p.project_name as name,
(select main_p.project_id from im_projects main_p
where main_p.tree_sortkey = tree_root_key(p.tree_sortkey)
) as context_id
from persons pe,
im_projects p,
im_projects super_p,
acs_rels r,
im_biz_object_members bom
where
super_p.parent_id is null and
p.parent_id is not null and -- Ignore assignments on a main project level
p.tree_sortkey between super_p.tree_sortkey and tree_right(super_p.tree_sortkey) and
super_p.project_status_id in (select * from im_sub_categories(76)) and
p.project_status_id in (select * from im_sub_categories(76)) and
r.rel_id = bom.rel_id and
r.object_id_one = p.project_id and
r.object_id_two = pe.person_id and
pe.person_id in (
-- Only report about persons assigned in main_p
select distinct
object_id_two
from acs_rels,
im_projects sub_p,
im_projects main_p
where object_id_one = sub_p.project_id and
sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and p.project_id not in (
-- Exclude assignments within main_p (handled in JS)
select sub_p.project_id
from im_projects sub_p,
im_projects main_p
where sub_p.tree_sortkey between main_p.tree_sortkey and tree_right(main_p.tree_sortkey) and
main_p.project_id = %main_project_id%
) and
p.end_date >= (select start_date from im_projects where project_id = %main_project_id%) and
p.start_date <= (select end_date from im_projects where project_id = %main_project_id%)
) t
where percentage > 0.0
order by
object_type,
object_id,
user_id
'
where report_code = 'rest_project_member_assignments_absences';
intranet-gantt-editor-master/tcl/ 0000775 0000000 0000000 00000000000 14412526641 0017361 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/tcl/intranet-gantt-editor-procs.tcl 0000664 0000000 0000000 00000003033 14412526641 0025433 0 ustar 00root root 0000000 0000000 # /packages/intranet-gantt-editor/tcl/intranet-gantt-editor.tcl
#
# Copyright (C) 2010-2013 ]project-open[
#
# All rights reserved. Please check
# https://www.project-open.com/license/ for details.
ad_library {
Gantt Editor library.
@author frank.bergmann@project-open.com
}
ad_proc -public gantt_editor_portlet {
-project_id:required
} {
Returns a HTML code with a Gantt editor for the project.
} {
# Only show for GanttProjects
if {[im_security_alert_check_integer -location "im_ganttproject_gantt_component" -value $project_id]} { return "" }
set project_type_id [util_memoize [list db_string project_type "select project_type_id from im_projects where project_id = $project_id" -default ""]]
if {![im_category_is_a $project_type_id [im_project_type_gantt]]} {
# Check if this is run from a tab in a non-Gantt project
# In this case we need to show an error message instead of a blank screen.
set plugin_id [im_opt_val -limit_to integer plugin_id]
if {"" ne $plugin_id} {
return "This project is not a Gantt project, but of type '[im_category_from_id $project_type_id]'.
Such projects don't have Gantt charts.
"
} else {
return ""
}
}
# Sencha check and permissions
if {![im_sencha_extjs_installed_p]} { return "" }
im_sencha_extjs_load_libraries
set params [list \
[list project_id $project_id] \
]
set result [ad_parse_template -params $params "/packages/intranet-gantt-editor/lib/gantt-editor"]
return [string trim $result]
}
intranet-gantt-editor-master/www/ 0000775 0000000 0000000 00000000000 14412526641 0017423 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/www/controller/ 0000775 0000000 0000000 00000000000 14412526641 0021606 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/www/controller/GanttButtonController.js 0000664 0000000 0000000 00000025744 14412526641 0026475 0 ustar 00root root 0000000 0000000 /*
* GanttTreePanelController.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* Deal with collapsible tree nodes, keyboard commands
* and the interaction with the GanttBarPanel.
*/
Ext.define('GanttEditor.controller.GanttButtonController', {
extend: 'Ext.app.Controller',
debug: true,
ganttTreePanel: null, // Set during init: left-hand task tree panel
ganttBarPanel: null, // Set during init: right-hand panel with Gantt sprites
taskTreeStore: null, // Set during init: treeStore with task data
ganttPanelContainer: null,
resizeController: null,
senchaPreferenceStore: null,
ganttTreePanelController: null,
buttonSave: null, //
refs: [
{ ref: 'ganttTreePanel', selector: '#ganttTreePanel' }
],
init: function() {
var me = this;
if (me.debug) { if (me.debug) console.log('PO.controller.gantt_editor.GanttButtonController: init'); }
// Listen to button press events
this.control({
'#buttonReloadGantt': { click: this.onButtonReload },
'#buttonSaveGantt': { click: this.onButtonSave },
'#buttonMaximizeGantt': { click: this.onButtonMaximize },
'#buttonMinimizeGantt': { click: this.onButtonMinimize },
'#buttonAddDependencyGantt': { click: this.onButton },
'#buttonBreakDependencyGantt': { click: this.onButton },
'#buttonSettingsGantt': { click: this.onButton },
scope: me.ganttTreePanel
});
// Listen to changes in the selction model in order to enable/disable the "delete" button.
me.ganttTreePanel.on('selectionchange', this.onTreePanelSelectionChange, me);
// Listen to a click into the empty space below the tree in order to add a new task
me.ganttTreePanel.on('containerclick', me.ganttTreePanel.onContainerClick, me.ganttTreePanel);
// Listen to special keys
me.ganttTreePanel.on('cellkeydown', this.onCellKeyDown, me.ganttTreePanel);
me.ganttTreePanel.on('beforecellkeydown', this.onBeforeCellKeyDown, me);
// Listen to vertical scroll events
var view = me.ganttTreePanel.getView();
view.on('bodyscroll',this.onTreePanelScroll, me);
// Listen to any changes in store records
me.taskTreeStore.on({'update': me.onTaskTreeStoreUpdate, 'scope': this});
// write_project_p is a global variable defined in gantt-editor.adp
var buttonSave = Ext.getCmp('buttonSaveGantt');
me.buttonSave = buttonSave;
var buttonLock = Ext.getCmp('buttonLockGantt');
if (1 == write_project_p) {
buttonSave.show();
buttonLock.hide();
} else {
buttonSave.hide();
buttonLock.show();
}
return this;
},
/**
* The user moves the scroll bar of the treePanel.
* Now scroll the ganttBarPanel in the same way.
*/
onTreePanelScroll: function(event, treeview) {
var me = this;
var ganttTreePanel = me.ganttTreePanel;
var ganttBarPanel = me.ganttBarPanel;
var view = ganttTreePanel.getView();
var scroll = view.getEl().getScroll();
// if (me.debug) console.log('GanttButtonController.onTreePanelScroll: Starting: '+scroll.top);
var ganttBarScrollableEl = ganttBarPanel.getEl(); // Ext.dom.Element that enables scrolling
ganttBarScrollableEl.setScrollTop(scroll.top);
// if (me.debug) console.log('GanttButtonController.onTreePanelScroll: Finished');
},
/**
* The user has reloaded the project data and therefore
* discarded any browser-side changes. So disable the
* "Save" button now.
*/
onButtonReload: function() {
var me = this;
if (me.debug) console.log('GanttButtonController.ButtonReload');
// var buttonSave = Ext.getCmp('buttonSaveGantt');
me.buttonSave.setDisabled(true);
},
/**
* The user has pressed the "Save" button - save and report
* any error messages
*/
onButtonSave: function() {
var me = this;
if (me.debug) console.log('GanttButtonController.ButtonSave: Starting');
// Make sure there are no duplicate tasks
me.ganttTreePanelController.treeRenumberStoreOldValues(); // Remember the current values of WBS field
me.ganttTreePanelController.treeRenumber();
// Fix wrong milestone_p field
me.taskTreeStore.tree.root.eachChild(function(taskModel) {
var milestoneP = taskModel.get('milestone_p');
var m = milestoneP;
switch (milestoneP) {
case "true": m = 't'; break;
case true: m = 't'; break;
case "false": m = 'f'; break;
case false: m = 'f'; break;
}
if (milestoneP != m) {
if (me.debug) console.log('GanttButtonController.ButtonSave: Fixing milestone_p from "'+milestoneP+'" to "'+m+'"');
taskModel.set('milestone_p', m);
}
var effortDrivenP = taskModel.get('effort_driven_p');
var m = effortDrivenP;
switch (effortDrivenP) {
case "true": m = 't'; break;
case true: m = 't'; break;
case "false": m = 'f'; break;
case false: m = 'f'; break;
}
if (effortDrivenP != m) {
if (me.debug) console.log('GanttButtonController.ButtonSave: Fixing effort_driven_p from "'+effortDrivenP+'" to "'+m+'"');
taskModel.set('effort_driven_p', m);
}
});
me.taskTreeStore.save({
success: function(batch, context) {
// work around issues in grid.panel that red "dirty" corners are not removed
me.taskTreeStore.tree.root.eachChild(function(taskModel) { taskModel.commit(); });
},
failure: function(batch, context) {
var msg = batch.proxy.reader.jsonData.message;
if (!msg) msg = 'Error message not available';
PO.Utilities.reportError("onButtonSave", 'Server error while saving: '+msg);
}
});
// Now block the "Save" button, unless some data are changed.
// var buttonSave = Ext.getCmp('buttonSaveGantt');
me.buttonSave.setDisabled(true);
if (me.debug) console.log('GanttButtonController.ButtonSave: Finished');
},
/**
* Some record of the taskTreeStore has changed.
* Enable the "Save" button to save these changes.
*/
onTaskTreeStoreUpdate: function(treeStore, model, action, affectedColumns, eOpts) {
var me = this;
if (me.debug) console.log('GanttButtonController.onTaskTreeStoreUpdate');
if (!affectedColumns || 0 == affectedColumns.length) return;
/*
// Check if read-only and abort in this case
var readOnly = me.senchaPreferenceStore.getPreferenceBoolean('read_only',true);
if (readOnly) {
var cnt = 0;
for (var idx in affectedColumns) {
var col = affectedColumns[idx];
console.log('GanttButtonController.onTaskTreeStoreUpdate: col='+col);
switch (col) {
case "expanded": break;
case "collapsed": break;
default: cnt++;
}
};
if (cnt > 0) {
me.ganttTreePanelController.readOnlyWarning();
return;
}
}
*/
// Enable the Save button
// var buttonSave = Ext.getCmp('buttonSaveGantt');
me.buttonSave.setDisabled(false); // Allow to "save" changes
// ToDo: This isn't always the case...
me.ganttBarPanel.needsRedraw = true; // Tell the ganttBarPanel to redraw with the next frame
},
/**
* Maximize Button: Expand the editor DIV, so that
* it fills the entire browser screen.
*/
onButtonMaximize: function() {
var me = this;
var buttonMaximize = Ext.getCmp('buttonMaximizeGantt');
var buttonMinimize = Ext.getCmp('buttonMinimizeGantt');
buttonMaximize.setVisible(false);
buttonMinimize.setVisible(true);
me.resizeController.onSwitchToFullScreen();
},
onButtonMinimize: function() {
var me = this;
var buttonMaximize = Ext.getCmp('buttonMaximizeGantt');
var buttonMinimize = Ext.getCmp('buttonMinimizeGantt');
buttonMaximize.setVisible(true);
buttonMinimize.setVisible(false);
me.resizeController.onSwitchBackFromFullScreen();
},
/**
* Control the enabled/disabled status of the (-) (Delete) button
*/
onTreePanelSelectionChange: function(view, records) {
var me = this;
if (me.debug) console.log('GanttButtonController.onTreePanelSelectionChange');
var buttonDelete = Ext.getCmp('buttonDeleteGantt');
if (1 == records.length) { // Exactly one record enabled
var record = records[0];
buttonDelete.setDisabled(!record.isLeaf());
} else { // Zero or two or more records enabled
buttonDelete.setDisabled(true);
}
},
/**
* Disable default tree key actions
*/
onBeforeCellKeyDown: function(me, htmlTd, cellIndex, record, htmlTr, rowIndex, e, eOpts) {
var me = this;
var keyCode = e.getKey();
var keyCtrl = e.ctrlKey;
if (me.debug) console.log('GanttButtonController.onBeforeCellKeyDown: code='+keyCode+', ctrl='+keyCtrl);
var panel = me.ganttTreePanel;
switch (keyCode) {
case 8: // Backspace 8
panel.onButtonDelete();
break;
case 37: // Cursor left
if (keyCtrl) {
// ToDo: moved to GanttTreePanelController
panel.onButtonReduceIndent();
return false; // Disable default action (fold tree)
}
break;
case 39: // Cursor right
if (keyCtrl) {
// ToDo: moved to GanttTreePanelController
panel.onButtonIncreaseIndent();
return false; // Disable default action (unfold tree)
}
break;
case 45: // Insert 45
me.ganttTreePanelController.onButtonAdd();
break;
case 46: // Delete 46
me.ganttTreePanelController.onButtonDelete();
break;
}
return true; // Enable default TreePanel actions for keys
},
/**
* Handle various key actions
*/
onCellKeyDown: function(table, htmlTd, cellIndex, record, htmlTr, rowIndex, e, eOpts) {
var me = this;
var keyCode = e.getKey();
var keyCtrl = e.ctrlKey;
// if (me.debug) console.log('GanttButtonController.onCellKeyDown: code='+keyCode+', ctrl='+keyCtrl);
}
});
intranet-gantt-editor-master/www/controller/GanttConfigController.js 0000664 0000000 0000000 00000003057 14412526641 0026420 0 ustar 00root root 0000000 0000000 /*
* GanttConfigController.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* Deal with actions of the configuration menu
*/
Ext.define('GanttEditor.controller.GanttConfigController', {
extend: 'Ext.app.Controller',
id: 'ganttConfigController',
refs: [
{ref: 'ganttBarPanel', selector: '#ganttBarPanel'},
{ref: 'ganttTreePanel', selector: '#ganttTreePanel'}
],
debug: false,
senchaPreferenceStore: null, // preferences
configMenuGanttEditor: null,
ganttBarPanel: null,
init: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttConfigController.init: Starting');
me.configMenuGanttEditor.on({
'click': me.onConfigClick,
'scope': this
});
if (me.debug) console.log('GanttEditor.controller.GanttConfigController.init: Finished');
},
onConfigClick: function(menu, item, e, eOpts) {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttConfigController.onConfigClick: Starting');
// Redraw immediately
me.ganttBarPanel.needsRedraw = true;
me.ganttBarPanel.redraw();
switch (item.id) {
case 'config_menu_show_project_findocs':
break;
}
if (me.debug) console.log('GanttEditor.controller.GanttConfigController.onConfigClick: Finished');
}
});
intranet-gantt-editor-master/www/controller/GanttSchedulingController.js 0000664 0000000 0000000 00000141206 14412526641 0027277 0 ustar 00root root 0000000 0000000 /*
* GanttTreePanelController.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* GanttSchedulingController
* Reacts to changes of start_date, end_date, work, assignments and possibly other
* task fields and modifies other tasks according to the specified scheduling type:
*
* - No scheduling
*
- Manually scheduled tasks
*
- Single project scheduling
*
- Multiproject scheduling
*
*/
Ext.define('GanttEditor.controller.GanttSchedulingController', {
extend: 'Ext.app.Controller',
debug: false,
'ganttTreePanel': null, // Defined during initialization
'taskTreeStore': null, // Defined during initialization
init: function() {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.init: Starting');
me.taskTreeStore.on({
'update': me.onTreeStoreUpdate, // Listen to any changes in store records
'scope': this
});
// Tell the GanttBarPanel about this controller
me.ganttBarPanel.ganttSchedulingController = me;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.init: Finished');
return this;
},
/* **************************************************************************************
Handlers for project tree changes
************************************************************************************** */
/**
* Some function has changed the TreeStore:
* Make sure to propagate the changes along dependencies
*/
onTreeStoreUpdate: function(treeStore, model, operation, fieldsChanged, event) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onTreeStoreUpdate: Starting');
me.suspendEvents(false);
treeStore.suspendEvents(false);
var dirty = false;
if (null != fieldsChanged) {
// Check the case that start- and end-date are changed together (move)
// A move is different from a change of either start- or end-date.
if (fieldsChanged.includes("start_date")) {
if (fieldsChanged.includes("end_date")) {
dirty = true;
fieldsChanged.splice(fieldsChanged.indexOf('start_date'), 1);
fieldsChanged.splice(fieldsChanged.indexOf('end_date'), 1);
}
}
fieldsChanged.forEach(function(fieldName) {
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onTreeStoreUpdate: Field changed='+fieldName);
switch (fieldName) {
case "assignees":
me.onAssigneesChanged(treeStore, model, operation, event);
dirty = true;
break;
case "start_date":
dirty = true;
break;
case "end_date":
me.onDurationChanged(treeStore, model, operation, event);
dirty = true;
break;
case "planned_units":
me.onPlannedUnitsChanged(treeStore, model, operation, event);
dirty = true;
break;
case "billable_units":
me.onBillableUnitsChanged(treeStore, model, operation, event);
dirty = true;
break;
case "parent_id":
// Task has new parent - indentation or un-indentation
dirty = true;
break;
case "leaf":
// A task has changed from leaf to tree or reverse:
// Don't do anything, this is handled with the "parent_id" field anyway
dirty = true;
break;
}
});
}
treeStore.resumeEvents();
me.resumeEvents();
if (dirty) {
me.ganttBarPanel.needsRedraw = true; // Force a redraw
var buttonSave = Ext.getCmp('buttonSaveGantt');
buttonSave.setDisabled(false); // Enable "Save" button
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onTreeStoreUpdate: Finished');
},
/**
* The user changed the planned units of an task.
* We now need to re-calculate the planned units towards the
* root of the tree.
*/
onPlannedUnitsChanged: function(treeStore, model) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onPlannedUnitsChanged: Starting');
var parent = model.parentNode;
if (!parent) return;
// Check planned units vs. assigned resources
me.checkTaskLength(treeStore, model);
// Calculate the sum of planned units of all nodes below parent
var plannedUnits = 0.0;
parent.eachChild(function(sibling) {
var siblingPlannedUnits = parseFloat(sibling.get('planned_units'));
if (!isNaN(siblingPlannedUnits)) {
plannedUnits = plannedUnits + siblingPlannedUnits;
}
});
// Check if we have to update the parent
if (parseFloat(parent.get('planned_units')) != plannedUnits) {
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onPlannedUnitsChanged: Setting parent.planned_units='+plannedUnits);
parent.set('planned_units', ""+plannedUnits);
// We now need to call onPlannedUnitsChanged recursively
// because we have disabled the events on the tree store
me.onPlannedUnitsChanged(treeStore, parent);
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onPlannedUnitsChanged: Finished');
},
/**
* The user changed the billable units of an task.
* We now need to re-calculate the billable units towards the
* root of the tree.
*/
onBillableUnitsChanged: function(treeStore, model) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onBillableUnitsChanged: Starting');
var parent = model.parentNode;
if (!parent) return;
// Calculate the sum of billable units of all nodes below parent
var billableUnits = 0.0;
parent.eachChild(function(sibling) {
var siblingBillableUnits = parseFloat(sibling.get('billable_units'));
if (!isNaN(siblingBillableUnits)) {
billableUnits = billableUnits + siblingBillableUnits;
}
});
// Check if we have to update the parent
if (parseFloat(parent.get('billable_units')) != billableUnits) {
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onBillableUnitsChanged: Setting parent.billable_units='+billableUnits);
parent.set('billable_units', ""+billableUnits);
// We now need to call onBillableUnitsChanged recursively
// because we have disabled the events on the tree store
me.onBillableUnitsChanged(treeStore, parent);
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onBillableUnitsChanged: Finished');
},
/**
* The assignees of a task has changed.
* Check if the length of the task is still valid.
*
* Returns true if we changed the model, false otherwise
*/
onAssigneesChanged: function(treeStore, model) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onAssigneesChanged: Starting');
var result = false;
var effortDrivenType = parseInt(model.get('effort_driven_type_id'));
if (isNaN(effortDrivenType)) {
effortDrivenType = parseInt(default_effort_driven_type_id); // Default is "Fixed Work" = 9722
}
if (isNaN(effortDrivenType)) effortDrivenType = 9722; // Default is "Fixed Work" = 9722
switch (effortDrivenType) {
case 9720: // Fixed Units
result = me.checkTaskLength(treeStore, model); // adjust the length of the task
break;
case 9721: // Fixed Duration
result = me.checkAssignedResources(treeStore, model); // adjust the percentage of the assigned resources
break;
case 9722: // Fixed Work
result = me.checkTaskLength(treeStore, model); // adjust the length of the task
break;
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onAssigneesChanged: Finished');
return result;
},
/**
* The duration of a task has changed.
* Check if the work or assignees of the task are still valid.
*
* Returns true if we changed the model, false otherwise
*/
onDurationChanged: function(treeStore, model) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onDurationChanged: Starting');
var result = false;
var effortDrivenType = parseInt(model.get('effort_driven_type_id'));
if (isNaN(effortDrivenType)) {
effortDrivenType = parseInt(default_effort_driven_type_id); // Default is "Fixed Work" = 9722
}
if (isNaN(effortDrivenType)) effortDrivenType = 9722; // Default is "Fixed Work" = 9722
switch (effortDrivenType) {
case 9720: // Fixed Units
result = me.checkAssignedResources(treeStore, model); // adjust the percentage of the assigned resources
break;
case 9721: // Fixed Duration
result = me.checkAssignedResources(treeStore, model); // adjust the percentage of the assigned resources
break;
case 9722: // Fixed Work
result = me.checkAssignedResources(treeStore, model); // adjust the percentage of the assigned resources
break;
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onDurationChanged: Finished');
return result;
},
/**
* The assignees of a task has changed.
* Check if the length of the task is still valid.
*/
onCreateDependency: function(dependencyModel) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onCreateDependency: Starting');
// sched initiated by next redraw()
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onCreateDependency: Finished');
},
/* **************************************************************************************
Scheduling Auxillary Functions
Determine the duration of a task
************************************************************************************** */
/**
* Calculate the first time in a "work session" after startTime when resources
* assignments change. This is the moment to start the next "session".
* In the future this will be controlled by a resource calendar.
* Currently (2020-09-07) we used hardcoded 9 to 17:00.
*
* Returns the time when resource assignments change after startTime.
*/
taskResourceChangeTime: function(startTime, taskModel, assignees) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourceChangeTime: Starting: '+startTime+' - '+new Date(startTime));
var startDate = new Date(startTime);
var startHour = startDate.getHours() * 3600.0*1000.0 + startDate.getMinutes() * 60.0*1000.0 + startDate.getSeconds()*1000.0 + startDate.getMilliseconds();
var startMidnightTime = startTime - startHour;
// console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourceChangeTime: midnight='+new Date(startMidnightTime));
// array of moments in time when resource availability changes
var changeTimeArray = [
startMidnightTime + 3600.0*1000.0*9.0,
startMidnightTime + 3600.0*1000.0*17.0,
startMidnightTime + 3600.0*1000.0*(24+9.0),
startMidnightTime + 3600.0*1000.0*(24+17.0) // work from 9:00 til 17:00
];
var changeTime = startTime;
// Search the position in the array that is bigger than startTime
var i = 0;
while (i < changeTimeArray.length) {
var arrayTime = changeTimeArray[i];
// console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourceChangeTime: startTime='+new Date(startTime));
// console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourceChangeTime: arrayTime='+new Date(arrayTime));
if (arrayTime > startTime) {
changeTime = arrayTime;
break;
}
i++;
}
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourceChangeTime: Finished: '+changeTime+' - '+new Date(changeTime));
return changeTime;
},
/**
* Calculate the number of resources working from the resource
* assignments of a task at a specific moment of time.
* Returns a floating number with the number of resources working.
*/
taskResourcesWorking: function(timeMoment, model, assignees) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourcesWorking: Starting: '+new Date(timeMoment));
// Calculate the percent assigned in total
var assignedPercent = 0.0
assignees.forEach(function(assig) { assignedPercent = assignedPercent + assig.percent });
// Check what time
var startDate = new Date(timeMoment);
var startHour = startDate.getHours() * 3600.0*1000.0 + startDate.getMinutes() * 60.0*1000.0 + startDate.getSeconds()*1000.0 + startDate.getMilliseconds();
var startMidnightTime = timeMoment - startHour;
// 9:00-17:00 - 100%, rest - 0%
if (startHour < 9.0*3600.0*1000.0 || startHour >= 17.0*3600.0*1000.0) assignedPercent = 0.0;
var dayOfWeek = startDate.getDay();
if (0 == dayOfWeek || 6 == dayOfWeek) assignedPercent = 0.0;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskResourcesWorking: Finished: resources='+assignedPercent / 100.0);
return assignedPercent / 100.0;
},
/**
* Calculate the new task duration based on resource assignments.
* Returns true the new task endTime
*/
taskForwardDuration: function(treeStore, model) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskForwardDuration: Starting');
var previousStartDate = PO.Utilities.pgToDate(model.get('start_date')); if (!previousStartDate) { return false; }
var previousEndDate = PO.Utilities.pgToDate(model.get('end_date')); if (!previousEndDate) { return false; }
var previousStartTime = previousStartDate.getTime();
var previousEndTime = previousEndDate.getTime();
var assignees = model.get('assignees');
var plannedUnits = model.get('planned_units');
if (0 == plannedUnits) { return previousEndTime; } // No units - no duration...
if (!plannedUnits) { return previousEndTime; } // No units - no duration...
// Check for no assignments ("manually scheduled task") and skip
var assignedPercent = 0.0
assignees.forEach(function(assig) { assignedPercent = assignedPercent + assig.percent });
if (0 == assignedPercent) { return previousEndTime; } // No assignments - "manually scheduled" task
// -------------------------------------------------------
// Get the first moment a resource is available.
// This may be the actual moment, or some time later.
var workStillToDo = plannedUnits * 3600.0 * 1000.0; // Work still to do in milli-seconds
var workSessionStartTime = previousStartTime;
while (workStillToDo > 0.0) {
// console.log('taskForwardDuration: sessionStart='+new Date(workSessionStartTime));
var workSessionEndTime = me.taskResourceChangeTime(workSessionStartTime, model, assignees); // First moment after workSessionStartTime that resources change
// console.log('taskForwardDuration: sessionEnd='+new Date(workSessionEndTime));
var workSessionDuration = workSessionEndTime - workSessionStartTime; // A period with constant resources
var workSessionResourcesWorking = me.taskResourcesWorking(workSessionStartTime, model, assignees); // Number of resources available at that moment
// console.log('taskForwardDuration: working='+workSessionResourcesWorking);
var workSessionWorkDone = workSessionDuration * workSessionResourcesWorking;
// We have to deal with the case of the last session, that may be longer than the time required to finish
if (workSessionWorkDone < workStillToDo) { // Not finished yet in this session
workStillToDo = workStillToDo - workSessionWorkDone; // Subtract work done in this session
} else {
workSessionDuration = workStillToDo / workSessionResourcesWorking;
workStillToDo = 0.0; // Nothing more to do!
}
workSessionStartTime = workSessionStartTime + workSessionDuration;
}
var endTime = workSessionStartTime;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.taskForwardDuration: Finished');
return endTime;
},
/* **************************************************************************************
"Check" procedures
************************************************************************************** */
/**
* Check the planned units vs. assigned resources percentage.
* Then follow the ResourceCalendar to calculate the new end_date.
*
* This function should only be called after changing work,
* assignments or duration of the task, not as part of the
* sched.
*
* Returns true if we had to modify the task, false otherwise
*/
checkTaskLength: function(treeStore, model) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkTaskLength: Starting');
var previousEndDate = PO.Utilities.pgToDate(model.get('end_date')); if (!previousEndDate) { return false; }
var previousEndTime = previousEndDate.getTime();
var newEndTime = me.taskForwardDuration(treeStore, model);
if (newEndTime == previousEndTime) return false; // skip if no change
// Write the newEndDate into model
var newEndDate = new Date(newEndTime);
var newEndDateString = PO.Utilities.dateToPg(newEndDate);
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkTaskLength: end_date='+newEndDateString);
model.set('end_date', newEndDateString);
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkTaskLength: Finished');
return true;
},
/**
* Check that the assigned resources correspond to duration and planned units.
* Then adapt assignment percentage uniformly.
*/
checkAssignedResources: function(treeStore, model) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkAssignedResources: Starting');
var startDate = PO.Utilities.pgToDate(model.get('start_date')); if (!startDate) return; // No date - no duration...
startDate.setHours(0,0,0,0);
var endDate = PO.Utilities.pgToDate(model.get('end_date')); if (!endDate) return; // No date - no duration...
var assignees = model.get('assignees');
var plannedUnits = model.get('planned_units'); if (!plannedUnits || 0 == plannedUnits) return; // No units - no duration...
// Calculate the percent assigned in total
var assignedPercent = 0.0
assignees.forEach(function(assig) {
assignedPercent = assignedPercent + assig.percent
});
if (0 == assignedPercent) { return; } // No assignments - nothing to fix
// Calculate the number of working time between start- and end-date
var startTime = startDate.getTime();
var endTime = endDate.getTime();
var workHours = 0.0; //
var now = startTime;
while (now < endTime) {
var day = new Date(now);
var dayOfWeek = day.getDay();
if (dayOfWeek == 6 || dayOfWeek == 0) {
// Weekend - just skip the day
} else {
// Weekday - add hours
workHours = workHours + 8;
}
now = now + 1000 * 3600 * 24;
}
// Calculate the total resources that need to be assigned
var assignedPercentNew = 100.0 * plannedUnits / workHours;
var assignmentFactor = assignedPercentNew / assignedPercent;
// Fix each assignment by the same factor
assignees.forEach(function(assig) {
assig.percent = Math.round(10.0 * assig.percent * assignmentFactor) / 10.0;
});
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkAssignedResources: Finished');
},
/**
* Finds a node in a tree by a custom function.
* @param {Object} node The node to start searching.
* @param {Function} fn A function which must return true if the passed Node is the required Node.
* @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
* @return {Ext.data.NodeInterface} The found child or null if none was found
*/
findNodeBy : function(node, fn, scope) {
// Check if the result is the node itself
var n = node;
if (fn.call(scope || n, n) === true) { return n; }
// Search the children
var me = this;
var cs = node.childNodes,
len = cs.length,
i = 0, res;
for (; i < len; i++) {
n = cs[i];
res = me.findNodeBy(n, fn, scope);
if (res !== null) { return res; }
}
return null;
},
/**
* Called by GanttTreePanel.redraw() before performing a redraw.
* Allows us to check the tree structure for sanity.
*/
onRedraw: function(ganttBarPanel) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onRedraw: Starting');
var fixP = true; // Yes, fix any issue
me.checkCyclicDependenciesInit(fixP); // Initialize directPreds, directSuccs and transParents
me.checkCyclicDependenciesParents(fixP); // Check for invalid parents being part of direct preds or succs
me.schedule(); // Fix schedule constraints
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.onRedraw: Finished');
},
/* **************************************************************************************
Check Cyclic Dependencies
************************************************************************************** */
/**
* Initialize data structures for cyclic dependency check of the project plan.
*
* This function initializes direct successors and predecessors
* data-structures needed for checking for cyclic dependencies
* plus calculate the transitive parents.
*
* - directSuccs: direct successors
*
- directPred: direct predecessors
*
- transParents: transitive parents
*
.
*/
checkCyclicDependenciesInit: function(fixP) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesInit: Starting');
// Iterate through all nodes
var treeStore = me.taskTreeStore;
var rootNode = treeStore.getRootNode();
// --------------------------------------------------------------------------------
// Initialize entire tree data-structures
rootNode.cascadeBy(function(task) {
task.directPreds = {};
task.directSuccs = {};
task.transParents = {};
delete task.transPreds;
delete task.transSuccs;
});
// --------------------------------------------------------------------------------
// Loop through all tasks and create direct preds and succs hashes
rootNode.cascadeBy(function(task) {
// Calculate transitive parents
var parentModel = task.parentNode;
while (parentModel) {
var parentId = ''+parentModel.get('id');
task.transParents[parentId] = parentModel;
parentModel = parentModel.parentNode; // Move up one level...
}
// Initialize with the list of direct predecessors
var repeatP = true;
while (repeatP) {
repeatP = false;
var predecessors = task.get('predecessors');
if (!predecessors instanceof Array) return;
for (var i = 0, len = predecessors.length; i < len; i++) {
var dependencyModel = predecessors[i];
var dependencyTypeId = dependencyModel.type_id; // an integer!
var predId = ''+dependencyModel.pred_id; // a string!
var predModel = me.findNodeBy(rootNode, function() {return (''+this.get('id') === predId);}, null);
if (!predModel) {
console.error("checkCyclicDependenciesInit: PredModel not found for: "+predId);
continue;
}
var succId = ''+dependencyModel.succ_id; // a string!
var succModel = me.findNodeBy(rootNode, function() { return (''+this.get('id') === succId);}, null);
if (!succModel) {
console.error("checkCyclicDependenciesInit: SuccModel not found for: "+succId);
continue;
}
succModel.directPreds[predId] = {predModel: predModel, depModel: dependencyModel};
predModel.directSuccs[succId] = {succModel: succModel, depModel: dependencyModel};
}
}
});
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesInit: Finished');
return false;
},
/**
* Check for parents being in the direct preds or succs.
* Returns true if it finds violating parents and deletes them.
*/
checkCyclicDependenciesParents: function(fixP) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesParents: Starting');
var treeStore = me.taskTreeStore;
var rootNode = treeStore.getRootNode();
var cyclicP = false;
// --------------------------------------------------------------------------------
// Loop through all tasks and check direct preds and succs for being in the node's parents
rootNode.cascadeBy(function(task) {
var taskId = ''+task.get('id');
for (var predId in task.directPreds) {
if (task.transParents[predId]) {
// alert('found pred in partnets');
me.checkCyclicDependenciesDelete(fixP, predId, taskId); // Delete the offending dependency
var cyclicP = true;
}
}
for (var succId in task.directSuccs) {
if (task.transParents[succId]) {
// alert('found succ in partnets');
me.checkCyclicDependenciesDelete(fixP, taskId, succId); // Delete the offending dependency
var cyclicP = true;
}
}
});
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesParents: Finished');
return cyclicP;
},
/**
* Delete a pred-succ relationship anywhere in the project hierarchy.
*
*/
checkCyclicDependenciesDelete: function(fixP, predId, succId) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesDelete: Starting');
if (!fixP) return false;
var treeStore = me.taskTreeStore;
var rootNode = treeStore.getRootNode();
// --------------------------------------------------------------------------------
// Search for dependencies from predId to succId and delete
rootNode.cascadeBy(function(task) {
var taskId = ''+task.get('id');
var predecessors = task.get('predecessors');
if (!predecessors instanceof Array) return;
var repeatP = true;
while (repeatP) {
repeatP = false;
for (var i = 0, len = predecessors.length; i < len; i++) {
var dependencyModel = predecessors[i];
var pred_id = ''+dependencyModel.pred_id;
var succ_id = ''+dependencyModel.succ_id;
if (''+predId === ''+pred_id && ''+succId === ''+succ_id) {
predecessors.splice(i,1); // Remove dependency
task.set('predecessors', predecessors); // Update task
console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesDelete: '+
'Deleting predecessor on task='+taskId+': '+predId+' -> '+succId);
repeatP = true;
break;
}
}
}
});
// --------------------------------------------------------------------------------
// Delete from direct and trans preds and succs
var predModel = me.findNodeBy(rootNode, function() {return (''+this.get('id') === ''+predId);}, null);
if (!predModel) {
alert("checkCyclicDependenciesDelete: PredModel not found for: "+predId);
return;
}
var succModel = me.findNodeBy(rootNode, function() { return (''+this.get('id') === ''+succId);}, null);
if (!succModel) {
alert("SuccModel not found for: "+succId);
return;
}
delete predModel.directSuccs[succId];
if (predModel.transSuccs) delete predModel.transSuccs[succId];
delete succModel.directPreds[predId];
if (succModel.transPreds) delete succModel.transPreds[predId];
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesDelete: Finished');
return false;
},
/**
* Calculate transitive preds and succs and check for
* cyclic loops in dependencies.
*
* Expects the transPreds and transSuccs to be initialized
* with direct succs/preds.
*/
checkCyclicDependenciesTransClosure: function(fixP) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesTransClosure: Starting');
var cyclicP = false;
var treeStore = me.taskTreeStore;
var rootNode = treeStore.getRootNode();
// --------------------------------------------------------------------------------
// Copy the direct preds/succs to the transitive preds/succs data structures
rootNode.cascadeBy(function(task) {
task.transPreds = {};
for (var predId in task.directPreds)
task.transPreds[predId] = task.directPreds[predId];
task.transSuccs = {};
for (var succId in task.directSuccs)
task.transSuccs[succId] = task.directSuccs[succId];
});
// Calculate the transitive predecessors:
// Loop through all tasks and add the predecessors of their predecessors to the hashes
var loopP = true;
while (loopP && !cyclicP) {
loopP = false;
rootNode.cascadeBy(function(task) { // Loop throught all nodes in the tree (tasks)
var taskId = ''+task.get('id');
var transPreds = task.transPreds;
for (var id in transPreds) {
// Loop through the prececessors preds and add them to the task's preds
var predModel = transPreds[id];
var predPreds = predModel.transPreds;
for (var predPredId in predPreds) {
if (!transPreds[predPredId]) { // check if the attribute already exists
loopP = true; // keep on looping...
var predPredModel = predPreds[predPredId];
transPreds[predPredId] = predPredModel;
// Found the ID of the object in the list of it's predecessors?
if (taskId === predPredId) cyclicP = true;
}
}
}
});
}
// Calculate the transitive successors:
// Loop through all tasks and add the successors of their successors
var loopP = true;
while (loopP && !cyclicP) {
loopP = false;
rootNode.cascadeBy(function(task) { // Loop throught all nodes in the tree (tasks)
var taskId = ''+task.get('id');
var transSuccs = task.transSuccs;
for (var id in transSuccs) { // Loop through all succecessors of the node
// Loop through the successors succs and add them to the task's succs
var succModel = transSuccs[id];
if (!succModel) {
alert('checkCyclicDependenciesTransClosure: succModel not found for Id='+id);
return; // This happens when linking to a summary task
}
var succSuccs = succModel.transSuccs;
for (var succSuccId in succSuccs) {
if (!transSuccs[succSuccId]) { // check if the attribute already exists
loopP = true; // keep on looping...
var succSuccModel = succSuccs[succSuccId];
transSuccs[succSuccId] = succSuccModel;
// Found the ID of the object in the list of it's succecessors?
if (taskId === succSuccId) cyclicP = true;
}
}
}
});
}
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependenciesTransClosure: Finished');
return cyclicP;
},
/**
* Check for cyclic dependencies in the project plan.
* First initialize transPreds and transSuccs hashes
* of transitive predecessors and successors with the
* direct preds/succs of the project task.
*
* Then use an iterative search to find the successors
* of successors and predecessors of preds etc.
*/
checkCyclicDependencies: function(fixP) {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependencies: Starting');
var startTime = new Date().getTime();
var cyclicP = false;
// Initialize transPreds, transSuccs and transParents
cyclicP = cyclicP || me.checkCyclicDependenciesInit(fixP);
// Check for invalid parents being part of direct preds or succs
cyclicP = cyclicP || me.checkCyclicDependenciesParents(fixP);
// Check for invalid parents being part of direct preds or succs
cyclicP = cyclicP || me.checkCyclicDependenciesTransClosure(fixP);
var endTime = new Date().getTime();
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.checkCyclicDependencies: Finished in '+(endTime-startTime));
return cyclicP;
},
/* **************************************************************************************
Scheduling
************************************************************************************** */
/**
* Returns the "time" milliseconds of a dependency lag
*/
dependencyLagTime: function(depModel) {
var me = this;
var diff = depModel.diff;
if (!diff) diff = 0.0;
var diff_factor = me.dependencyPropertyPanel.dependencyFormatFactor(depModel.diff_format_id);
var result = 1000.0 * diff * diff_factor;
if (result != 0.0) {
console.log('dependencyLagTime: lag='+result);
}
return result;
},
/**
* Check the planned units vs. assigned resources percentage.
* Then follow the ResourceCalendar to calculate the new end_date.
*
* Returns an array of changed nodes.
*/
scheduleTaskDuration: function(treeStore, model) {
var me = this;
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskDuration: Starting');
if (model.hasChildNodes()) { return []; }
var previousStartDate = PO.Utilities.pgToDate(model.get('start_date')); if (!previousStartDate) { return []; }
previousStartDate.setHours(0,0,0,0);
var previousEndDate = PO.Utilities.pgToDate(model.get('end_date')); if (!previousEndDate) { return []; }
var previousEndTime = previousEndDate.getTime();
var assignees = model.get('assignees');
var plannedUnits = model.get('planned_units');
if (0 == plannedUnits) { return []; } // No units - no duration...
if (!plannedUnits) { return []; } // No units - no duration...
// Calculate the percent assigned in total
var assignedPercent = 0.0
assignees.forEach(function(assig) { assignedPercent = assignedPercent + assig.percent });
if (0 == assignedPercent) { return []; } // No assignments - "manually scheduled" task
// Calculate the new endDate
var newEndTime = me.taskForwardDuration(treeStore, model);
if (newEndTime == previousEndTime) return []; // skip if no change
// Write the newEndDate into model
var newEndDate = new Date(newEndTime);
var newEndDateString = PO.Utilities.dateToPg(newEndDate);
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskDuration: end_date='+newEndDateString);
model.set('end_date', newEndDateString);
if (me.debug && me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskDuration: Finished');
return [model];
},
/**
* Check that the dependency constraint "dep" is met with pred and succ.
* Otherwise shift the start_date of succ.
*
* Returns an array of changed nodes.
*/
scheduleTaskToTask: function(treeStore, pred, succ, dep) {
var me = this;
if (me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskToTask: Starting');
var predStartDate = PO.Utilities.pgToDate(pred.get('start_date')); if (!predStartDate) { return false; }
var predEndDate = PO.Utilities.pgToDate(pred.get('end_date')); if (!predEndDate) { return false; }
var succStartDate = PO.Utilities.pgToDate(succ.get('start_date')); if (!succStartDate) { return false; }
var succEndDate = PO.Utilities.pgToDate(succ.get('end_date')); if (!succEndDate) { return false; }
var dependencyTypeId = dep.type_id; // 9660=FF, 9662=FS, 9664=SF, 9666=SS
if (dependencyTypeId == 9650) dependencyTypeId = 9662; // compatibility
// If the start of succ is before the end of pred...
var changedNodes = [];
switch (dependencyTypeId) {
case 9660: // Finish-to-Finish
var diff = predEndDate.getTime() - succEndDate.getTime();
break;
case 9662: // Finish-to-Start
var diff = predEndDate.getTime() - succStartDate.getTime();
break;
case 9664: // Start-to-Finish
var diff = predStartDate.getTime() - succEndDate.getTime();
break;
case 9666: // Start-to-Start
var diff = predStartDate.getTime() - succStartDate.getTime();
break;
default:
alert('scheduleTaskToTask: found dependencyTypeId='+dependencyTypeId+': undefined dependency type');
return;
}
// Calculage the time (milliseconds) of the lag time in the dependency
var lagTime = me.dependencyLagTime(dep);
diff = diff + lagTime;
if (diff > 0) {
// Round the diff to the next hour and check if the difference is max. 1 minute
var diffRoundedByHour = Math.round(diff / (3600.0 * 1000.0)) * (3600.0 * 1000.0);
if (Math.abs(diff - diffRoundedByHour) <= 60.0 * 1000.0) {
diff = diffRoundedByHour; // The difference is less then a minute
}
var newSuccStartDate = new Date(succStartDate.getTime() + diff); // Add diff to the start and end of succ
var newSuccEndDate = new Date(succEndDate.getTime() + diff);
// Write the new dates to succ
var startDateString = PO.Utilities.dateToPg(newSuccStartDate);
var endDateString = PO.Utilities.dateToPg(newSuccEndDate);
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskToTask: start_date='+startDateString+', end_date='+endDateString);
succ.set('start_date', startDateString);
succ.set('end_date', endDateString);
changedNodes.push(succ);
}
if (me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleTaskToTask: Finished');
return changedNodes;
},
/**
* Make sure a task or summary is moved correctly after a pred task or summary.
*
* Returns an array of changed nodes.
*/
scheduleXToY: function(treeStore, pred, succ, dep) {
var me = this;
if (me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleAfter: Starting');
var changedNodes = [];
pred.cascadeBy(function(predChild) {
if (predChild.hasChildNodes()) return; // only relation between leaf tasks
succ.cascadeBy(function(succChild) {
if (succChild.hasChildNodes()) return; // only relation between leaf tasks
var nodes = me.scheduleTaskToTask(treeStore, predChild, succChild, dep);
for (var i = 0; i < nodes.length; i++) changedNodes.push(nodes[i]);
});
});
if (me.debug > 1) console.log('PO.controller.gantt_editor.GanttSchedulingController.scheduleAfter: Finished');
return changedNodes;
},
/**
* Fix constraints in the schedule
*
* - Tasks with work and assignees need to have matching duration.
* - Parents start and end with their first and last task respectively
* - Tasks with a predecessors start after the end of the predecessor,
* if the dependency is end-to-start. Otherwise we ignore the dependency.
*
* Constraints:
* Instead of "scheduling", we really just check that no constraints
* are broken and adjust the network:
* - Finish-to-End relationships between tasks of various levels
* - Summary vs. sub-task.
* - what about dependency from sub-task to summary=?
*
*/
schedule: function() {
var me = this;
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.schedule: Starting');
var startTime = new Date().getTime();
var treeStore = me.taskTreeStore;
var rootNode = treeStore.getRootNode();
me.suspendEvents(false);
treeStore.suspendEvents(false);
// Initialize with the list of all tasks in the tree
var changedNodes = [];
var changedNodesHash = {};
rootNode.cascadeBy(function(task) {
changedNodes.push(task);
var taskId = task.get('id');
changedNodesHash[taskId] = task;
});
// Iterate through all nodes until we reach the end of successor chains
var iterationCount = 0;
while (changedNodes.length > 0) {
iterationCount++;
var changedNode = changedNodes.shift(); // Get and remove the first element from stack
var taskId = changedNode.get('id');
delete changedNodesHash.taskId;
var nodeId = ''+changedNode.get('id');
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.schedule: '+
'Iteration='+iterationCount+': Checking id='+nodeId+', name='+changedNode.get('project_name'));
// Perform basic check on the new node
me.scheduleTaskDuration(treeStore, changedNode); // adjust the length of the task
// Loop through all direct successors
var directSuccs = changedNode.directSuccs;
for (var succId in directSuccs) {
var succModel = directSuccs[succId].succModel;
var depModel = directSuccs[succId].depModel;
nodes = me.scheduleXToY(treeStore, changedNode, succModel, depModel);
for (var i = 0; i < nodes.length; i++) {
var task = nodes[i];
var taskId = task.get('id');
if (changedNodesHash[taskId]) { continue; } // avoid pushing the same node twice!
changedNodes.push(task);
}
}
}
// Move summary.start_date and summary.end_date to fit children.
// This is necessary because the algorithm above only checks for hard constraints.
rootNode.cascadeBy(function(summary) { // Loop throught all children
if (!summary.hasChildNodes()) return; // Skip if not a summary
var summaryStartDate = PO.Utilities.pgToDate(summary.get('start_date')); if (!summaryStartDate) { return false; }
var summaryStartTime = summaryStartDate.getTime();
var summaryEndDate = PO.Utilities.pgToDate(summary.get('end_date')); if (!summaryEndDate) { return false; }
var summaryEndTime = summaryEndDate.getTime();
var minChildStartTime = new Date('2099-12-31').getTime(); // Initiate with maximum values
var maxChildEndTime = new Date('2000-01-01').getTime();
summary.cascadeBy(function(child) {
if (child.get('id') == summary.get('id')) return; // Skip if it's the same object
var childStartDate = PO.Utilities.pgToDate(child.get('start_date')); if (!childStartDate) { return false; }
var childStartTime = childStartDate.getTime();
if (childStartTime < minChildStartTime) minChildStartTime = childStartTime;
var childEndDate = PO.Utilities.pgToDate(child.get('end_date')); if (!childEndDate) { return false; }
var childEndTime = childEndDate.getTime();
if (childEndTime > maxChildEndTime) maxChildEndTime = childEndTime;
});
if (minChildStartTime != summaryStartTime) { // Update summary to fit children
var summary_start_date = PO.Utilities.dateToPg(new Date(minChildStartTime));
summary.set('start_date', summary_start_date);
}
if (maxChildEndTime != summaryEndTime) { // Update summary to fit children
var summary_end_date = PO.Utilities.dateToPg(new Date(maxChildEndTime));
summary.set('end_date', summary_end_date);
}
});
treeStore.resumeEvents();
me.resumeEvents();
var endTime = new Date().getTime();
if (me.debug) console.log('PO.controller.gantt_editor.GanttSchedulingController.schedule: '+
'Finished in '+(endTime-startTime));
}
});
intranet-gantt-editor-master/www/controller/GanttTreePanelController.js 0000664 0000000 0000000 00000061550 14412526641 0027074 0 ustar 00root root 0000000 0000000 /*
* GanttTreePanelController.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* Deal with collapsible tree nodes, keyboard commands
* and the interaction with the GanttBarPanel.
*/
Ext.define('GanttEditor.controller.GanttTreePanelController', {
extend: 'Ext.app.Controller',
requires: ['Ext.app.Controller'],
refs: [
{ref: 'ganttBarPanel', selector: '#ganttBarPanel'},
{ref: 'ganttTreePanel', selector: '#ganttTreePanel'}
],
ganttTreePanel: null,
senchaPreferenceStore: null,
init: function() {
var me = this;
this.control({
'#ganttTreePanel': {
'itemcollapse': this.onItemCollapse,
'itemexpand': this.onItemExpand
},
'#buttonReduceIndentGantt': { click: this.onButtonReduceIndent},
'#buttonIncreaseIndentGantt': { click: this.onButtonIncreaseIndent},
'#buttonAddGantt': { click: this.onButtonAdd},
'#buttonDeleteGantt': { click: this.onButtonDelete},
// Redraw GanttBars after changing the configuration
'#config_menu_show_project_dependencies': { click: this.redrawGanttBarPanel},
'#config_menu_show_project_assigned_resources': { click: this.redrawGanttBarPanel}
});
// Listen to drop events from tree drag-and-drop
if (null != me.ganttTreePanel) {
var ganttTreeView = me.ganttTreePanel.getView();
ganttTreeView.on({
'drop': me.onGanttTreePanelDrop,
'scope': this
});;
}
},
/**
* Show a warning that the GanttEditor is Beta
*/
readOnlyWarning: function() {
var me = this;
console.log('PO.controller.GanttTreePanelController.readOnlyWarning');
if (0 == write_project_p) {
Ext.Msg.alert("Read-Only Mode",
"You don't have write permissions on this project.
"+
"You won't be able to save your changes.
"+
"Please contact the project manager and request write permissions." +
"
"
);
} else {
Ext.Msg.alert('This software is Beta',
'This software is in Beta state and contains a number of known
'+
'and unknown issues (please see the "This is Beta") menu.
' +
'
' +
'However, many users have asked for this feature and use this
' +
'Gantt Editor already successfully, working around existing issues.
' +
'
' +
'In order to start working with the Gantt Editor, please uncheck
' +
'the Configuration -> Read Only option.
'
);
}
},
/**
* Request a redraw of the Gantt bars
*/
redrawGanttBarPanel: function() {
var me = this;
console.log('PO.controller.GanttTreePanelController.redrawGanttBarPanel');
var ganttBarPanel = me.getGanttBarPanel();
ganttBarPanel.needsRedraw = true;
},
/**
* The user has collapsed a super-task in the GanttTreePanel.
* We now save the 'c'=closed status using a ]po[ URL.
* These values will appear in the TaskTreeStore.
*/
onItemCollapse: function(taskModel) {
var me = this;
var object_id = taskModel.get('id');
Ext.Ajax.request({
url: '/intranet/biz-object-tree-open-close.tcl',
params: { 'object_id': object_id, 'open_p': 'c' }
});
// me.getGanttBarPanel().redraw();
me.getGanttBarPanel().needsRedraw = true;
},
/**
* The user has expanded a super-task in the GanttTreePanel.
* Please see onItemCollapse for further documentation.
*/
onItemExpand: function(taskModel) {
var me = this;
if (me.debug) console.log('PO.class.GanttDrawComponent.onItemExpand: ');
// Remember the new state
var object_id = taskModel.get('id');
Ext.Ajax.request({
url: '/intranet/biz-object-tree-open-close.tcl',
params: { 'object_id': object_id, 'open_p': 'o' }
});
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
},
/**
* Move the task more to the right if possible.
*
* Take the node just above the selected one and
* make this node a child of it.
*/
onButtonIncreaseIndent: function() {
var me = this;
if (me.debug) console.log('GanttTreePanelController.onButtonIncreaseIndent');
// Check if read-only and abort in this case
// var readOnly = me.senchaPreferenceStore.getPreferenceBoolean('read_only',true);
// if (readOnly) { me.readOnlyWarning(); return; }
var ganttTreePanel = this.getGanttTreePanel();
var selectionModel = ganttTreePanel.getSelectionModel();
var lastSelected = selectionModel.getLastSelected();
var lastSelectedParent = lastSelected.parentNode;
if (null == lastSelectedParent) { return; } // We can't indent the root element
var lastSelectedIndex = lastSelectedParent.indexOf(lastSelected);
var prevNodeIndex = lastSelectedIndex -1;
if (prevNodeIndex < 0) { return; } // We can't indent the root element
var prevNode = lastSelectedParent.getChildAt(prevNodeIndex);
me.treeRenumberStoreOldValues(); // Remember the current values of WBS field
// Add the item as a child of the prevNode
prevNode.set('leaf', false);
prevNode.appendChild(lastSelected); // Add to the previous node as a child
prevNode.expand();
var prevNodeId = ""+prevNode.get('id');
// Set the parent_id of the indented item
lastSelected.set('parent_id', prevNodeId); // This should trigger a Gantt re-schedule
ganttTreePanel.getView().focusNode(lastSelected); // Focus back on the task for keyboard commands
me.treeRenumber(); // Update the tree's task numbering
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
// ToDo: Re-schedule the tree
},
/**
* Move the task more to the left if possible.
*/
onButtonReduceIndent: function() {
var me = this;
if (me.debug) console.log('GanttTreePanelController.onButtonReduceIndent');
// Check if read-only and abort in this case
// var readOnly = me.senchaPreferenceStore.getPreferenceBoolean('read_only',true);
// if (readOnly) { me.readOnlyWarning(); return; }
var ganttTreePanel = this.getGanttTreePanel();
var selectionModel = ganttTreePanel.getSelectionModel();
var lastSelected = selectionModel.getLastSelected();
var lastSelectedParent = lastSelected.parentNode;
if (null == lastSelectedParent) { return; } // We can't indent the root element
var lastSelectedParentParent = lastSelectedParent.parentNode;
if (null == lastSelectedParentParent) { return; } // We can't indent the root element
var lastSelectedParentIndex = lastSelectedParentParent.indexOf(lastSelectedParent);
// Baseline the old WBS values
me.treeRenumberStoreOldValues(); // Remember the current values of WBS field
// Move the child
lastSelectedParentParent.insertChild(lastSelectedParentIndex+1, lastSelected);
// Check if the parent has now become a leaf
var parentNumChildren = lastSelectedParent.childNodes.length;
if (0 == parentNumChildren) {
lastSelectedParent.set('leaf', true);
}
ganttTreePanel.getView().focusNode(lastSelected); // Focus back on the task for keyboard commands
me.treeRenumber(); // Update the tree's task numbering
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
},
/**
* "Add" (+) button pressed.
* Insert a new task in the position of the last selection.
*/
onButtonAdd: function() {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttTreePanelController.onButtonAdd: ');
// Check if read-only and abort in this case
// var readOnly = me.senchaPreferenceStore.getPreferenceBoolean('read_only',true);
// if (readOnly) { me.readOnlyWarning(); return; }
var ganttTreePanel = me.getGanttTreePanel();
var rowEditing = ganttTreePanel.plugins[0];
var taskTreeStore = ganttTreePanel.getStore();
var root = taskTreeStore.getRootNode();
me.treeRenumberStoreOldValues(); // Remember the current values of WBS field
rowEditing.cancelEdit();
var selectionModel = ganttTreePanel.getSelectionModel();
var lastSelected = selectionModel.getLastSelected();
var lastSelectedParent = null;
if (null == lastSelected) {
lastSelected = root; // This should be the main project
lastSelectedParent = root; // This is the "virtual" and invisible root of the tree
} else {
lastSelectedParent = lastSelected.parentNode;
}
// Create a model instance and decorate with NodeInterface
var parent_id = lastSelected.get('parent_id');
if ("" == parent_id) { parent_id = lastSelected.get('id'); }
var r = Ext.create('PO.model.timesheet.TimesheetTask', {
parent_id: ""+parent_id,
company_id: lastSelected.get('company_id'),
project_status_id: "76", // Status: Open - status store uses strings!
project_type_id: "100", // Type: Gantt Task
iconCls: 'icon-task',
assignees: []
});
var rNode = root.createNode(r); // Convert model into tree node
rNode.set('leaf', true); // The new task is a leaf (show different icon)
var appendP = false;
if (!selectionModel.hasSelection()) { appendP = true; }
if (root == lastSelected) { appendP = true; }
if (lastSelected.getDepth() <= 1) { appendP = true; } // Don't allow to add New Task before the root.
if (appendP) {
root.appendChild(rNode); // Add the task at the end of the root
} else {
// lastSelectedParent.insertBefore(rNode, lastSelected); // Insert into tree
// lastSelectedParent.appendChild(rNode); // Insert into tree
var index = lastSelectedParent.indexOf(lastSelected); // Get the index of the last selected
lastSelectedParent.insertChild(index+1,rNode);
}
r.set('parent_id', ""+parent_id); // model uses strings!!
r.set('percent_completed', ""+0);
r.set('planned_units', ""+0);
r.set('billable_units', ""+0);
r.set('material_id', ""+default_material_id);
r.set('uom_id', ""+default_uom_id);
r.set('project_name', 'New Task');
r.set('work', ""+8); // 8 hours by default
var nowDate = PO.Utilities.dateToPg(new Date()).substring(0,10);
r.set('start_date', nowDate+" 00:00:00"); // Indicates start of the day at 00:00:00
r.set('end_date', nowDate+" 23:59:59"); // Same as start_date, but indicates 23:59:59
r.set('effort_driven_type_id', ""+default_effort_driven_type_id);
// Get a server-side object_id for the task
Ext.Ajax.request({
url: '/intranet-rest/data-source/next-object-id',
success: function(response){
var json = Ext.JSON.decode(response.responseText);
var object_id_string = json.data.object_id;
var object_id = parseInt(object_id_string);
r.set('id', object_id);
r.set('project_id', object_id_string);
r.set('task_id', object_id_string);
if ("" == r.get('project_nr')) r.set('project_nr', "task_"+object_id_string);
},
failure: function(response){
Ext.Msg.alert('Error retreiving object_id from server',
'This error may lead to data-loss for your project. Error: '+response.responseText);
}
});
// For first task in project: Update the root
lastSelectedParent.set('leaf', false); // Parent is not a leaf anymore
lastSelectedParent.expand(); // Expand parent if not yet expanded
// Start the column editor
selectionModel.deselectAll();
selectionModel.select([rNode]);
rowEditing.startEdit(rNode, 0);
// select/Highlight the name of the newly created task in order to speedup entering new tasks manually
var columnHeader = rowEditing.context.column;
var ed = rowEditing.getEditor(rNode, columnHeader);
var inputEl = ed.field.inputEl;
inputEl.dom.select();
me.treeRenumber(); // Update the tree's task numbering
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
},
/**
* "Delete" (-) button pressed.
* Delete the currently selected task from the tree.
*/
onButtonDelete: function() {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttTreePanelController.onButtonDelete: ');
// Get all the necessary objects around
var ganttTreePanel = me.getGanttTreePanel();
var rowEditing = ganttTreePanel.plugins[0];
var taskTreeStore = ganttTreePanel.getStore();
var selectionModel = ganttTreePanel.getSelectionModel();
var rootNode = taskTreeStore.getRootNode(); // Get the absolute root
var lastSelected = selectionModel.getLastSelected();
var lastSelectedParent = lastSelected.parentNode;
var lastSelectedIndex = lastSelectedParent.indexOf(lastSelected);
var lastSelectedId = lastSelected.get('id');
// ----------------------------------------------------------
// Check for dependencies and delete
// Iterate through all children of the root node and check if they are visible
if (me.debug) console.log('PO.view.gantt.GanttTreePanelController.onButtonDelete: about to delete taskId='+lastSelectedId+' from tree');
rootNode.cascadeBy(function(model) {
var predecessors = model.get('predecessors');
for (var i = 0; i < predecessors.length; i++) {
var pred = predecessors[i];
var predId = pred.id;
var predPredId = pred.pred_id;
if (predPredId == lastSelectedId) {
predecessors.splice(i,1);
if (me.debug) console.log('PO.view.gantt.GanttTreePanelController.onButtonDelete: deleted: '+
'lastSelectedId='+lastSelectedId+', predId='+predId+', predPredId='+predPredId);
}
};
});
// ----------------------------------------------------------
// Work with selection in the task tree
me.treeRenumberStoreOldValues(); // Remember the current values of WBS field
rowEditing.cancelEdit(); // Just in case the editor was active...
lastSelected.remove(); // Remove the selected element
var numChildren = lastSelectedParent.childNodes.length; // Check if we deleted the last task of a parent.
if (0 == numChildren) { // This parent then becomes a normal task again.
lastSelectedParent.set('leaf', true); // Parent is not a leaf anymore
}
// Select the next node
var newNode = lastSelectedParent.getChildAt(lastSelectedIndex);
if (typeof(newNode) == "undefined") {
lastSelectedIndex = lastSelectedIndex -1;
if (lastSelectedIndex < 0) { lastSelectedIndex = 0; }
newNode = lastSelectedParent.getChildAt(lastSelectedIndex);
}
// lastSelected was the last child of it's parent, so select the parent.
if (typeof(newNode) == "undefined") {
selectionModel.select(lastSelectedParent);
} else {
newNode = lastSelectedParent.getChildAt(lastSelectedIndex);
selectionModel.select(newNode);
}
// Redraw, renumber and enable save button
me.treeRenumber(); // Update the tree's task numbering
// ----------------------------------------------------------
// Finish off
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
var buttonSave = Ext.getCmp('buttonSaveGantt');
buttonSave.setDisabled(false); // Enable "Save" button
},
/**
* The user has clicked below the last task.
* We will interpret this as the request to create a new task at the end.
*/
onContainerClick: function() {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttTreePanelController.onContainerClick: ');
// Clear the selection in order to force adding the task at the bottom
var ganttTreePanel = me.getGanttTreePanel();
var selectionModel = ganttTreePanel.getSelectionModel();
selectionModel.deselectAll();
me.onButtonAdd();
},
/**
* Drop events inside the task tree.
* We need to update the parent_id and sort_order of the task.
*/
onGanttTreePanelDrop: function(node, data, overModel, dropPosition, eOpts) {
var me = this;
if (me.debug) console.log('PO.controller.GanttTreePanelController.onGanttTreePanelDrop: Starting');
var records = data.records; // tasks dropped into new position
var parent = null;
me.treeRenumberStoreOldValues(); // Remember the current values of WBS field
// Update the parent_id of the task
switch (dropPosition) {
case "before":
parent = overModel.parentNode;
break;
case "after":
parent = overModel.parentNode;
break;
case "append":
parent = overModel;
break;
default:
alert("GanttTreePanelController.onGanttTreePanelDrop: Unknown dropPosition="+dropPosition);
break;
}
var parent_id = parent.get('id');
if (null != parent_id && "" != parent_id) {
records.forEach(function(record) {
record.set('parent_id', parent_id);
});
}
me.treeRenumber(); // Update the tree's task numbering
me.getGanttBarPanel().needsRedraw = true; // Force delayed redraw
if (me.debug) console.log('PO.controller.GanttTreePanelController.onGanttTreePanelDrop: Finished');
},
/**
* Before updating the WBS numbers in treeRenumber,
* we need to store the old values baseline in order
* to check if they were manually modified.
*/
treeRenumberStoreOldValues: function() {
var me = this;
// if (me.debug) console.log('PO.controller.GanttTreePanelController.treeRenumberStoreOldValues: Starting');
var ganttTreePanel = me.getGanttTreePanel();
var taskTreeStore = ganttTreePanel.getStore();
var rootNode = taskTreeStore.getRootNode(); // Get the absolute root
var sortOrder = 0;
var last_wbs = [];
// Iterate through all children of the root node and check if they are visible
rootNode.cascadeBy(function(model) {
// Fix the parent_id reference to the tasks's parent node
var parent = model.parentNode;
var parent_wbs = "";
if (!!parent) {
var parentId = ""+parent.get('id');
var parent_id = ""+model.get('parent_id');
if (parentId != parent_id && 0 != sortOrder && "root" != parentId) {
model.set('parent_id', parentId);
}
// Get the WBS code from the parent node
parent_wbs = parent.get('project_wbs');
}
// Recalculate the WBS code
var depth = model.getDepth()-1;
if (depth >= 0) {
var last_wbs_slice = last_wbs.slice(0,depth+1);
while (last_wbs_slice.length <= depth) {
last_wbs_slice.push(0);
}
var last_wbs_digit = last_wbs_slice[depth];
last_wbs_slice[depth] = last_wbs_digit + 1;
last_wbs = last_wbs_slice;
var wbs = last_wbs.join('.');
// Store this as the old autmatic WBS
// into a field outside the normal model
model.data.project_wbs_old_automatic = wbs;
}
sortOrder++;
});
// if (me.debug) console.log('PO.controller.GanttTreePanelController.treeRenumberStoreOldValues: Finished');
},
/**
* Update the numbering of the Gantt tasks after a
* change that affects the ordering including
* drag-and-drop events.
*
* Please note that this function expects that you
* run treeRenumberStoreOldValues() before you
* actually performed a tree change. This is necessary
* in order to detect manual changes in the WBS,
* which should be preserved.
*/
treeRenumber: function() {
var me = this;
// if (me.debug) console.log('PO.controller.GanttTreePanelController.treeRenumber: Starting');
var ganttTreePanel = me.getGanttTreePanel();
var taskTreeStore = ganttTreePanel.getStore();
var rootNode = taskTreeStore.getRootNode(); // Get the absolute root
var sortOrder = 0;
var duplicateHash = {};
var last_wbs = [];
// Iterate through all children of the root node and check if they are visible
rootNode.cascadeBy(function(model) {
// Check for duplicates
var name = "" + (model.get('project_name').replace(/\([0-9]+\)/, '')).trim();
var id = model.get('id');
var list = duplicateHash[name] || [];
list.push(model);
duplicateHash[name] = list;
// Fix the sort_order sequence of tasks
var modelSortOrder = model.get('sort_order');
if (""+modelSortOrder != ""+sortOrder && 0 != sortOrder) {
model.set('sort_order', ""+sortOrder);
}
// Fix the parent_id reference to the tasks's parent node
var parent = model.parentNode;
var parent_wbs = "";
if (!!parent) {
var parentId = ""+parent.get('id');
var parent_id = ""+model.get('parent_id');
if (parentId != parent_id && 0 != sortOrder && "root" != parentId) {
model.set('parent_id', parentId);
}
// Get the WBS code from the parent node
parent_wbs = parent.get('project_wbs');
}
// Recalculate the WBS code
var depth = model.getDepth()-1;
if (depth >= 0) {
var last_wbs_slice = last_wbs.slice(0,depth+1);
while (last_wbs_slice.length <= depth) {
last_wbs_slice.push(0);
}
var last_wbs_digit = last_wbs_slice[depth];
last_wbs_slice[depth] = last_wbs_digit + 1;
last_wbs = last_wbs_slice;
var newWbs = last_wbs.join('.');
var curWbs = model.get('project_wbs');
var automaticWbsBeforeAction = model.data.project_wbs_old_automatic;
if (!automaticWbsBeforeAction) automaticWbsBeforeAction = "";
// No change? Then just skip...
if (!(newWbs === curWbs)) {
// Preserve manual changes to the WBS. Only update the WBS if the
// previous value was generated automatically.
var manualChangeP = (curWbs !== automaticWbsBeforeAction);
if ("" === curWbs) manualChangeP = false; // No WBS yet, probably a new task
if ("" === automaticWbsBeforeAction) manualChangeP = false; // Some inconsistency, shouldn't appear...
if (!manualChangeP) {
model.set('project_wbs', newWbs);
}
}
}
sortOrder++;
});
// Rename duplicate task names
Object.keys(duplicateHash).forEach(function(key) {
var modelList = duplicateHash[key];
if (modelList.length > 1) {
// Rename the items
for (var i = 0; i < modelList.length; i++) {
modelList[i].set('project_name', key+" ("+ (i+1) +")");
}
}
});
// if (me.debug) console.log('PO.controller.GanttTreePanelController.treeRenumber: Finished');
}
});
intranet-gantt-editor-master/www/controller/GanttZoomController.js 0000664 0000000 0000000 00000046416 14412526641 0026145 0 ustar 00root root 0000000 0000000 /*
* GanttZoomController.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* Deal with zoom In/Out buttons, sizing the X axis according to the
* project and centering the scroll bars to show the entire projects.
* All related to the GanttBarPanel.
*
* Zoom is determined by the following variables of the GanttBarPanel
* (defined in AbstractGanttPanel):
* - axisStartX: Always 0, so this shouldn't be a variable, actually....
* - axisEndX: End of the axis in pixel terms. Determines the size of the drawing surface.
* Increasing axixEndX has a zoom-in effect, and a scroll bar will appear if
* axisEndX > ganttBarPanel.width.
* - axisStartDate: Set to project_start_date minus a margin by default.
* - axisEndDate: Set to project_end_date plus a margin by default.
* Start- and EndDate are set wider for zooming out of the project,
* once axisEndX = GanttParPanel.width.
*
* 1. Decrease the size of the surface, until the surface has the size of the GanttBarPanel.
* 2. Set the size of the surface exactly to the size of the GanttBarPanel.
* 3. Now start changing axisStartDate and axisEndDate in order to increase the time
* span shown with the surface.width pixels.
*
* Perform the opposed steps when zooming in.
*/
Ext.define('GanttEditor.controller.GanttZoomController', {
extend: 'Ext.app.Controller',
id: 'ganttZoomController',
refs: [
{ref: 'ganttBarPanel', selector: '#ganttBarPanel'},
{ref: 'ganttTreePanel', selector: '#ganttTreePanel'}
],
debug: false,
senchaPreferenceStore: null, // preferences
zoomFactor: 1.5, // Fast or slow zooming? 2.0 is fast, 1.2 is very slow
zoomOnEntireProjectMarginFactor: 0.2, // Margin to leave at the left and right
zoomOnEntireProjectMinMarginDays: 2, // Minimum margin in days
// ToDo: Do we need to update zooming after a resize?
init: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.init: Starting');
// Catch events from three zoom buttons
me.control({
'#buttonZoomInGantt': { click: me.onButtonZoomIn },
'#buttonZoomOutGantt': { click: me.onButtonZoomOut },
'#buttonZoomCenterGantt': { click: me.onButtonZoomCenter },
'#buttonZoomLeftGantt': { click: me.onButtonZoomLeft },
'#buttonZoomRightGantt': { click: me.onButtonZoomRight }
});
// Catch scroll events
var ganttBarPanel = me.getGanttBarPanel();
var scrollableEl = ganttBarPanel.getEl();
scrollableEl.on({
'scroll': me.onHorizontalScroll,
'scope': this
});
// Check if there is a state stored from a previous session.
var persistedP = me.restoreFromPreferenceStore();
if (!persistedP) {
// Otherwise show the entire project as a default
me.zoomOnEntireProject();
}
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.init: Finished');
},
/**
* Set axis and scroll configuration as stored
* in the SenchaPreferenceStore from the last session
*/
restoreFromPreferenceStore: function() {
var me = this;
if (me.debug) console.log('GanttZoomController.restoreFromPreferenceStore: Started');
var ganttBarPanel = me.getGanttBarPanel();
var persistedP = false;
me.senchaPreferenceStore.each(function(model) {
var preferenceKey = model.get('preference_key');
var preferenceValue = model.get('preference_value');
var preferenceInt = parseInt(preferenceValue);
switch (preferenceKey) {
case 'scrollX': ganttBarPanel.scrollX = preferenceInt; persistedP = true; break;
case 'axisStartTime': ganttBarPanel.axisStartDate = new Date(preferenceInt); persistedP = true; break;
case 'axisEndTime': ganttBarPanel.axisEndDate = new Date(preferenceInt); persistedP = true; break;
case 'axisStartX': ganttBarPanel.axisStartX = preferenceInt; persistedP = true; break;
case 'axisEndX': ganttBarPanel.axisEndX = preferenceInt; persistedP = true; break;
};
});
if (me.debug) console.log('GanttZoomController.restoreFromPreferenceStore: Finished');
return persistedP;
},
/**
* Set zoom so that the entire project fits on the surface
* without scroll bar.
*/
zoomOnEntireProject: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.zoomEntireProject: Started');
var ganttBarPanel = me.getGanttBarPanel();
// calculate min- and max dates of the tasks
var reportStartTime = new Date('2099-12-31').getTime();
var reportEndTime = new Date('2000-01-01').getTime();
// Accept that some tasks may have not been scheduled
var rootNode = ganttBarPanel.objectStore.getRootNode();
rootNode.cascadeBy(function(model) {
var start_date = model.get('start_date'); if (!start_date) return;
var end_date = model.get('end_date'); if (!end_date) return;
var startTime = PO.Utilities.pgToDate(start_date).getTime();
var endTime = PO.Utilities.pgToDate(end_date).getTime();
if (startTime < reportStartTime) reportStartTime = startTime;
if (endTime > reportEndTime) reportEndTime = endTime;
});
// Default values for axis startDate and endDate
var oneDayMiliseconds = 24 * 3600 * 1000;
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
var panelHeight = panelBox.height;
ganttBarPanel.axisStartX = 0;
ganttBarPanel.axisEndX = panelWidth;
ganttBarPanel.surface.setSize(panelWidth,panelHeight);
var marginTime = (reportEndTime - reportStartTime) * me.zoomOnEntireProjectMarginFactor; // space left and right
var minMarginTime = me.zoomOnEntireProjectMinMarginDays * oneDayMiliseconds;
if (marginTime < minMarginTime) marginTime = minMarginTime;
ganttBarPanel.axisStartDate = new Date(reportStartTime - marginTime);
ganttBarPanel.axisEndDate = new Date(reportEndTime + marginTime);
// persist the changes
me.senchaPreferenceStore.setPreference('axisStartTime', ganttBarPanel.axisStartDate.getTime());
me.senchaPreferenceStore.setPreference('axisEndTime', ganttBarPanel.axisEndDate.getTime());
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX);
me.senchaPreferenceStore.setPreference('scrollX', 0);
me.getGanttBarPanel().needsRedraw = true; // request a redraw
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.zoomEntireProject: Finished');
},
/**
* A user(!) has changed the horizontal scrolling by moving the scroll-bar.
* Store the new value persistently on the server.
* This is all we need to do, because the Browser handles the scrolling for us.
* Debugging is disabled because there many be many events per second.
*/
onHorizontalScroll: function(scrollEvent, htmlElement, eOpts) {
var me = this;
var ganttBarPanel = me.getGanttBarPanel();
var scrollableEl = ganttBarPanel.getEl();
var scrollX = scrollableEl.getScrollLeft();
me.senchaPreferenceStore.setPreference('scrollX', ''+Math.round(scrollX));
},
/**
* Zoom In - The user has pressed the (+) button.
*/
onButtonZoomIn: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomIn: Starting');
var ganttBarPanel = me.getGanttBarPanel();
var oneDayMiliseconds = 24 * 3600 * 1000;
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
// Calculate the duration of the diagram time axix
var reportStartTime = ganttBarPanel.reportStartDate.getTime();
var reportEndTime = ganttBarPanel.reportEndDate.getTime();
var marginTime = (reportEndTime - reportStartTime) * me.zoomOnEntireProjectMarginFactor; // space at the left and right
var minMarginTime = me.zoomOnEntireProjectMinMarginDays * oneDayMiliseconds;
if (marginTime < minMarginTime) marginTime = minMarginTime;
reportStartTime = reportStartTime - marginTime;
reportEndTime = reportEndTime + marginTime;
var reportDurationTime = reportEndTime - reportStartTime;
// Calculate the duration of the Gantt axis in current zoom state
var ganttStartTime = ganttBarPanel.axisStartDate.getTime();
var ganttEndTime = ganttBarPanel.axisEndDate.getTime();
var ganttDurationTime = ganttEndTime - ganttStartTime;
var diffDays = Math.round(ganttDurationTime / oneDayMiliseconds / 6.0);
if (diffDays <= 1) diffDays = 1;
ganttStartTime = ganttStartTime + diffDays * oneDayMiliseconds;
ganttEndTime = ganttEndTime - diffDays * oneDayMiliseconds;
// Check if we are zoomed out in terms of time beyond the project duration.
// In this case we will narrow start- and endTime:
if (ganttDurationTime > reportDurationTime) {
// Undoing 3rd level of zoom - reduce the axisStartDate and axisEndDate
ganttBarPanel.axisStartDate = new Date(ganttStartTime);
ganttBarPanel.axisEndDate = new Date(ganttEndTime);
var newGanttDurationTime = ganttEndTime - ganttStartTime;
} else {
// Undoing 1. level of zoom
ganttBarPanel.axisEndX = me.zoomFactor * ganttBarPanel.axisEndX;
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX); // Persist the new zoom parameters
}
// Unbalanced? Just zoom on the entire project then...
if (ganttStartTime < reportStartTime || ganttEndTime > reportEndTime) {
// me.zoomOnEntireProject(); // center and fit zoom.
}
me.getGanttBarPanel().needsRedraw = true;
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX); // Persist the new zoom parameters
me.senchaPreferenceStore.setPreference('axisStartTime', ganttBarPanel.axisStartDate.getTime());
me.senchaPreferenceStore.setPreference('axisEndTime', ganttBarPanel.axisEndDate.getTime());
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomIn: Finished');
},
/**
* Zoom Out - The user has pressed the (-) button.
*/
onButtonZoomOut: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomOut: Starting');
var ganttBarPanel = me.getGanttBarPanel();
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
// This is the check for the 3. zoom phase when we already reduced the
// size of the surface: The user wants to zoom out beyond the duration
// of the project, so we have to change the axis start- and end-dates:
var zoomingBeyondSurface = false;
if (ganttBarPanel.axisEndX <= panelWidth) {
var startTime = ganttBarPanel.axisStartDate.getTime();
var endTime = ganttBarPanel.axisEndDate.getTime();
var diffDays = Math.round((endTime - startTime) / 24.0 / 3600.0 / 1000.0 / 3.0);
if (diffDays <= 1) diffDays = 1;
startTime = startTime - diffDays * 24.0 * 3600 * 1000;
endTime = endTime + diffDays * 24.0 * 3600 * 1000;
ganttBarPanel.axisStartDate = new Date(startTime);
ganttBarPanel.axisEndDate = new Date(endTime);
zoomingBeyondSurface = true;
}
// 1. level of zooming - try reducing the size of the surface area
var endX = ganttBarPanel.axisEndX / me.zoomFactor;
// 2. level of zooming - we just reached the limit of the surface size.
if (endX < panelWidth) {
endX = panelWidth;
// center the project
if (!zoomingBeyondSurface) {
me.zoomOnEntireProject();
}
}
// Set the size of the axis and as a result the size of the drawing surface.
ganttBarPanel.axisEndX = endX;
me.getGanttBarPanel().needsRedraw = true;
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX); // Persist the new zoom parameters
me.senchaPreferenceStore.setPreference('axisStartTime', ganttBarPanel.axisStartDate.getTime());
me.senchaPreferenceStore.setPreference('axisEndTime', ganttBarPanel.axisEndDate.getTime());
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomOut: Finished');
},
/**
* Zoom Right - The user has pressed the (<) button.
*/
onButtonZoomRight: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomRight: Starting');
var ganttBarPanel = me.getGanttBarPanel();
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
var startTime = ganttBarPanel.axisStartDate.getTime();
var endTime = ganttBarPanel.axisEndDate.getTime();
var diffTime = endTime - startTime;
startTime = startTime + (diffTime / 2.0);
endTime = endTime + (diffTime / 2.0);
ganttBarPanel.axisStartDate = new Date(startTime);
ganttBarPanel.axisEndDate = new Date(endTime);
me.getGanttBarPanel().needsRedraw = true;
// Persist changes
me.senchaPreferenceStore.setPreference('axisStartTime', startTime);
me.senchaPreferenceStore.setPreference('axisEndTime', endTime);
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomRight: Finished');
},
/**
* Zoom Left - The user has pressed the (<) button.
*/
onButtonZoomLeft: function() {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomLeft: Starting');
var ganttBarPanel = me.getGanttBarPanel();
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
var startTime = ganttBarPanel.axisStartDate.getTime();
var endTime = ganttBarPanel.axisEndDate.getTime();
var diffTime = endTime - startTime;
startTime = startTime - (diffTime / 2.0);
endTime = endTime - (diffTime / 2.0);
ganttBarPanel.axisStartDate = new Date(startTime);
ganttBarPanel.axisEndDate = new Date(endTime);
me.getGanttBarPanel().needsRedraw = true;
// Persist changes
me.senchaPreferenceStore.setPreference('axisStartTime', startTime);
me.senchaPreferenceStore.setPreference('axisEndTime', endTime);
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomLeft: Finished');
},
/**
* Zoom towards a task selected by the user.
* Set the scroll bar so that the task is shown in
* the middle of the GanttBarPanel, but don't zoom
* in or out.
*/
zoomOnSelectedTask: function(selectedTask) {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.zoomOnSelectedTask: Starting');
var ganttBarPanel = me.getGanttBarPanel();
// Calculate the "midX" X-coordinate of the middle of the current task
var start_date = selectedTask.get('start_date');
var end_date = selectedTask.get('end_date');
if ("" == start_date || "" == end_date) return; // Skip if there are issues with start or end_date
var startDate = PO.Utilities.pgToDate(start_date);
var endDate = PO.Utilities.pgToDate(end_date);
var startX = ganttBarPanel.date2x(startDate);
var endX = ganttBarPanel.date2x(endDate);
var midX = Math.round((startX + endX) / 2);
// Compare the middle of the Gantt bar with the middle of the screen
var ganttSize = ganttBarPanel.getSize();
var ganttMidX = Math.round(ganttSize.width / 2);
var scrollX = midX - ganttMidX;
if (scrollX < 0) scrollX = 0;
if (scrollX > (ganttBarPanel.axisEndX - 100)) scrollX = ganttBarPanel.axisEndX - 100;
var scrollableEl = ganttBarPanel.getEl(); // Ext.dom.Element that enables scrolling
scrollableEl.setScrollLeft(scrollX);
me.senchaPreferenceStore.setPreference('scrollX', scrollX); // write new scrollX as a default into a persistent preference
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.zoomOnSelectedTask: Finished');
},
/**
* Zoom Center
* Set the scroll bar so that the currently selected task
* (or the main task if no task is selected) is shown in
* the middle of the GanttBarPanel.
*/
onButtonZoomCenter: function() {
var startDate, endDate, startX, endX, midX;
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomCenter: Starting');
var ganttTreePanel = me.getGanttTreePanel();
var ganttBarPanel = me.getGanttBarPanel();
// Is a task selected? Otherwise center around the entire project
var selectionModel = ganttTreePanel.getSelectionModel();
var lastSelected = selectionModel.getLastSelected();
// fraber 2020-03-05: ZoomOnSelectedTask doesn't work well, now just zooming on project
me.zoomOnEntireProject();
/*
if (lastSelected) {
me.zoomOnSelectedTask(lastSelected);
} else {
me.zoomOnEntireProject();
}
*/
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX); // Persist the new zoom parameters
me.senchaPreferenceStore.setPreference('axisStartTime', ganttBarPanel.axisStartDate.getTime());
me.senchaPreferenceStore.setPreference('axisEndTime', ganttBarPanel.axisEndDate.getTime());
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onButtonZoomCenter: Finished');
},
/**
* Somebody pressed the "Fullscreen" button...
* This function is called by the ResizeController.
*/
onSwitchToFullScreen: function () {
var me = this;
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onSwitchToFullScreen: Starting');
var ganttBarPanel = me.getGanttBarPanel();
var panelBox = ganttBarPanel.getBox();
var panelWidth = panelBox.width;
var panelHeight = panelBox.height;
var surfaceWidth = ganttBarPanel.axisEndX;
if (surfaceWidth < panelWidth) {
ganttBarPanel.axisEndX = panelWidth;
ganttBarPanel.surface.setSize(panelWidth,panelHeight);
// persist the changes
me.senchaPreferenceStore.setPreference('axisEndX', ganttBarPanel.axisEndX);
me.senchaPreferenceStore.setPreference('scrollX', 0);
ganttBarPanel.needsRedraw = true; // request a redraw
}
if (me.debug) console.log('GanttEditor.controller.GanttZoomController.onSwitchToFullScreen: Finished');
}
});
intranet-gantt-editor-master/www/index.vuh 0000664 0000000 0000000 00000002364 14412526641 0021263 0 ustar 00root root 0000000 0000000 # /packages/intranet-gantt-editor/www/index.vuh
#
# Copyright (C) 2003 - 2017 ]project-open[
#
# All rights reserved. Please check
# https://www.project-open.com/license/ for details.
# ---------------------------------------------------------------
# Defaults & Security
# ---------------------------------------------------------------
# Get parameters
set user_id [auth::require_login]
set server_root [acs_root_dir]
global tcl_platform
set platform $tcl_platform(platform)
# ---------------------------------------------------------------
# Parse the URL information
# ---------------------------------------------------------------
# This index.vuh file will receive all calls to the /sencha-core/
# url for file that don't exist
# Get the entire URL and decompose
set url [ns_conn url]
set url_pieces [split $url "/"]
# set url_file [lindex $url_pieces 2]
# set url_file_ext [lindex [split $url_file "."] 1]
set url_file [join [lrange $url_pieces 2 end] "/"]
set url_file_body [lindex [split $url_file "."] 0]
set params [list]
set adp_file "/packages/intranet-gantt-editor/www/${url_file_body}"
#ad_return_complaint 1 $adp_file
set result [ad_parse_template -params $params $adp_file]
doc_return 200 "application/javascript" $result
ad_script_abort
intranet-gantt-editor-master/www/store/ 0000775 0000000 0000000 00000000000 14412526641 0020557 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/www/store/AbsenceAssignmentStore.js 0000664 0000000 0000000 00000003151 14412526641 0025523 0 ustar 00root root 0000000 0000000 /*
* /intranet-gantt-editor/www/store/AbsenceAssignmentStore.js
*
* Copyright (C) 2014 ]project-open[
* All rights reserved. Please see
* https://www.project-open.com/license/sencha/ for details.
*
* A store with the list of absences or assignments of the users
* in the project.
*/
Ext.define('GanttEditor.store.AbsenceAssignmentModel', {
extend: 'Ext.data.Model',
fields: [
'id',
'object_id',
'object_type',
'user_id',
'user_name',
'start_date',
'end_date',
'percentage',
'name',
'context_id',
'context'
]
});
Ext.define('GanttEditor.store.AbsenceAssignmentStore', {
storeId: 'absenceAssignmentStore',
extend: 'Ext.data.Store',
model: 'GanttEditor.store.AbsenceAssignmentModel', // Uses standard Absence as model
autoLoad: false,
remoteFilter: true, // Do not filter on the Sencha side
pageSize: 100000, // Load all projects, no matter what size(?)
proxy: {
type: 'rest', // Standard ]po[ REST interface for loading
url: '/intranet-reporting/view',
appendId: true,
timeout: 300000,
extraParams: {
report_code: 'rest_project_member_assignments_absences',
format: 'json',
main_project_id: '0', // to be overwritten
// deref_p: '1' // We don't need company_name etc.
// This should be overwrittten during load.
},
reader: {
type: 'json', // Tell the Proxy Reader to parse JSON
root: 'data', // Where do the data start in the JSON file?
totalProperty: 'total' // Total number of tickets for pagination
},
writer: {
type: 'json' // Allow Sencha to write ticket changes
}
}
});
intranet-gantt-editor-master/www/view/ 0000775 0000000 0000000 00000000000 14412526641 0020375 5 ustar 00root root 0000000 0000000 intranet-gantt-editor-master/www/view/GanttBarPanel.adp 0000664 0000000 0000000 00000161167 14412526641 0023561 0 ustar 00root root 0000000 0000000 /*
* GanttBarPanel.js
*
* Copyright (c) 2011 - 2015 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* Gantt panel for GanttEditor, displaying the list of
* Gantt task of a single project.
* Relies on GanttTreePanel for the Y position of the Gantt bars.
*/
Ext.define('GanttEditor.view.GanttBarPanel', {
extend: 'PO.view.gantt.AbstractGanttPanel',
requires: [
'PO.view.gantt.AbstractGanttPanel',
'Ext.draw.Component',
'Ext.draw.Surface',
'Ext.layout.component.Draw'
],
ganttSchedulingController: null, // set later
debug: false,
taskBBoxHash: {}, // Hash array from object_ids -> Start/end point
taskModelHash: {}, // Start and end date of tasks
preferenceStore: null,
arrowheadSize: 5,
needsRedraw: false, // Set this instead of initiating a redraw()
needsReschedule: false, // Set this instead of initiating a schedule()
/**
* Starts the main editor panel as the right-hand side
* of a project grid and a cost center grid for the departments
* of the resources used in the projects.
*/
initComponent: function() {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.initComponent: Starting');
this.callParent(arguments);
// Catch the moment when the "view" of the Project grid
// is ready in order to draw the GanttBars for the first time.
// The view seems to take a while...
me.objectPanel.on({
'viewready': me.onProjectGridViewReady,
'sortchange': me.onProjectGridSortChange,
'scope': this
});
// Catch the event that the object got moved
me.on({
'spriterightclick': me.onSpriteRightClick,
'resize': me.redraw,
'scope': this
});
// Redraw GanttBars when all events are handled
Ext.globalEvents.on("idle", this.onIdle, me); // Fires before return to browser
// Iterate through all children of the root node and check if they are visible
me.objectStore.on({
'datachanged': me.onObjectStoreDataChanged,
'scope': this
});
var rootNode = me.objectStore.getRootNode();
rootNode.cascadeBy(function(model) {
var id = model.get('id');
me.taskModelHash[id] = model; // Quick storage of models
});
this.addEvents('move');
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.initComponent: Finished');
},
/**
* Called before passing control back to the Browser.
* Used to initiate a redraw() if necessary.
* No logging, because this routine is called so frequently.
*/
onIdle: function() {
var me = this;
if (me.needsRedraw) {
me.redraw();
me.needsRedraw = false; // mark the "dirty" flat as cleaned
}
},
/**
* Some data in the object store have changed.
* Normally we need to redraw here.
*/
onObjectStoreDataChanged: function(event, eOpts) {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onObjectStoreDataChanged: Starting');
// Modified, removed and new records in the store
var modRec = event.getModifiedRecords();
var remRec = event.getRemovedRecords();
var newRec = event.getNewRecords();
// Redraw for collapse/expand is handled explicitly
// in onItemCollapse/onItemExpand.
// Optimization: Don't redraw in these cases.
if (0 == remRec.length + newRec.length + modRec.length) { return; }
if (0 == remRec.length + newRec.length && 1 == modRec.length) {
var mod = modRec[0].modified;
if ('expanded' in mod) { return; }
}
me.needsRedraw = true;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onObjectStoreDataChanged: Finished');
},
/**
* The list of tasks is (finally...) ready to be displayed.
* We need to wait until this one-time event in in order to
* set the width of the surface and to perform the first redraw().
* Write the selection preferences into the SelModel.
*/
onProjectGridViewReady: function() {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectGridViewReady: Starting');
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectGridViewReady: Finished');
},
onProjectGridSortChange: function(headerContainer, column, direction, eOpts) {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectGridSortChange: Starting');
me.needsRedraw = true;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectGridSortChange: Finished');
},
/**
* The user has right-clicked on a sprite.
*/
onSpriteRightClick: function(event, sprite) {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onSpriteRightClick: Starting: '+ sprite);
if (null == sprite) { return; } // Something went completely wrong...
var dndConfig = sprite.dndConfig;
if (!!dndConfig) {
this.onProjectRightClick(event, sprite);
return;
}
var dependencyModel = sprite.dependencyModel;
if (!!dependencyModel) {
this.onDependencyRightClick(event, sprite);
return;
}
if (me.debug) { console.log('PO.view.gantt.GanttBarPanel.onSpriteRightClick: Unknown sprite:'); console.log(sprite); }
},
/**
* The user has right-clicked on a dependency.
*/
onDependencyRightClick: function(event, sprite) {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onDependencyRightClick: Starting: '+ sprite);
if (null == sprite) { return; } // Something went completely wrong...
// Menu for right-clicking a dependency arrow.
if (!me.dependencyContextMenu) {
me.dependencyContextMenu = Ext.create('Ext.menu.Menu', {
id: 'dependencyContextMenu',
style: {overflow: 'visible'}, // For the Combo popup
dependencyModel: sprite.dependencyModel,
items: [{
text: '@edit_dependency_l10n@',
handler: function() {
if (me.debug) console.log('dependencyContextMenu.editDependency: ');
var dependencyModel = this.ownerCt.dependencyModel;
var succId = dependencyModel.succ_id;
var succModel = me.taskModelHash[succId]; // Dependencies are stored as succModel.predecessors
var dependencyPropertyPanel = Ext.getCmp('ganttDependencyPropertyPanel');
dependencyPropertyPanel.setValue(dependencyModel, succModel);
dependencyPropertyPanel.setActiveTab('dependencyPropertyFormGeneral');
dependencyPropertyPanel.show();
}
}, {
text: '@delete_dependency_l10n@',
handler: function() {
if (me.debug) console.log('dependencyContextMenu.deleteDependency: ');
var dependencyModel = this.ownerCt.dependencyModel;
var predId = dependencyModel.pred_id;
var succId = dependencyModel.succ_id;
var predModel = me.taskModelHash[predId]; // This should be empty!!
var succModel = me.taskModelHash[succId]; // Dependencies are stored as succModel.predecessors
var predecessors = succModel.get('predecessors');
var orgPredecessorsLen = predecessors.length
for (i = 0; i < predecessors.length; i++) {
var el = predecessors[i];
if (el.pred_id == predId) {
predecessors.splice(i,1);
}
}
if (predecessors.length != orgPredecessorsLen) { me.needsRedraw = true; }
succModel.set('predecessors',predecessors);
// Move the task to now()
succModel.setDirty(); // set(...) may not set dirty
var oldStartDate = PO.Utilities.pgToDate(succModel.get('start_date'));
var oldEndDate = PO.Utilities.pgToDate(succModel.get('end_date'));
var oldDateDiff = Math.abs(oldEndDate.getTime() - oldStartDate.getTime());
var newStartDate = new Date();
var newEndDate = new Date(newStartDate.getTime() + oldDateDiff);
me.needsRedraw = true;
// set the new dates
succModel.set('start_date', PO.Utilities.dateToPg(newStartDate));
succModel.set('end_date', PO.Utilities.dateToPg(newEndDate));
}
}]
});
}
me.dependencyContextMenu.dependencyModel = sprite.dependencyModel; // context menu may be executed more than once with different deps
me.dependencyContextMenu.showAt(event.getXY());
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onDependencyRightClick: Finished');
},
/**
* The user has right-clicked on a project bar
*/
onProjectRightClick: function(event, sprite) {
var me = this;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectRightClick: '+ sprite);
if (null == sprite) { return; } // Something went completely wrong...
},
/**
* Move the project forward or backward in time.
* This function is called by onMouseUp as a
* successful "drop" action of a drag-and-drop.
*/
onProjectMove: function(projectSprite, xDiff) {
var me = this;
var projectModel = projectSprite.dndConfig.model;
if (!projectModel) return;
var projectId = projectModel.get('id');
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectMove: Starting');
var bBox = me.dndBaseSprite.getBBox(); // Get the current coordinates of the moved Gantt bar
var diffTime = xDiff * (me.axisEndDate.getTime() - me.axisStartDate.getTime()) / (me.axisEndX - me.axisStartX);
var diffDays = Math.round(diffTime / 24.0 / 3600.0 / 1000.0);
var startDate = PO.Utilities.pgToDate(projectModel.get('start_date')); if (!startDate) startDate = new Date();
var endDate = PO.Utilities.pgToDate(projectModel.get('end_date')); if (!endDate) endDate = new Date();
var startTime = startDate.getTime();
var endTime = endDate.getTime();
// Save original start- and end time in non-model variables
if (!projectModel.orgStartTime) {
projectModel.orgStartTime = startTime;
projectModel.orgEndTime = endTime;
}
startTime = startTime + diffDays * 24.0 * 3600 * 1000;
endTime = endTime + diffDays * 24.0 * 3600 * 1000;
var newStartDate = new Date(startTime);
var newEndDate = new Date(endTime);
// projectModel.set('start_date', PO.Utilities.dateToPg(newStartDate));
// projectModel.set('end_date', PO.Utilities.dateToPg(newEndDate));
projectModel.set({
'start_date': PO.Utilities.dateToPg(newStartDate),
'end_date': PO.Utilities.dateToPg(newEndDate)
});
me.needsRedraw = true;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectMove: Finished');
},
/**
* Move the end-date of the project forward or backward in time.
* This function is called after a successful drag-and-drop operation
* of the "resize handle" of the bar.
*/
onProjectResize: function(projectSprite, xDiff) {
var me = this;
var projectModel = projectSprite.dndConfig.model;
if (!projectModel) return;
var projectId = projectModel.get('id');
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectResize: Starting');
var bBox = me.dndBaseSprite.getBBox();
var diffTime = Math.floor(1.0 * xDiff * (me.axisEndDate.getTime() - me.axisStartDate.getTime()) / (me.axisEndX - me.axisStartX));
var endTime = PO.Utilities.pgToDate(projectModel.get('end_date')).getTime();
// Save original start- and end time in non-model variables
if (!projectModel.orgEndTime) {
projectModel.orgEndTime = endTime;
}
endTime = endTime + diffTime;
var endDate = new Date(endTime);
projectModel.set('end_date', PO.Utilities.dateToPg(endDate));
me.needsRedraw = true;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectResize: Finished');
},
/**
* Move the end of the percent_completed bar according to mouse-up position.
*/
onProjectPercentResize: function(projectSprite, percentSprite) {
var me = this;
var projectModel = projectSprite.dndConfig.model;
if (!projectModel) return;
var projectId = projectModel.get('id');
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectPercentResize: Starting');
var projectBBox = projectSprite.getBBox();
var percentBBox = percentSprite.getBBox();
var projectWidth = projectBBox.width;
if (0 == projectWidth) projectWidth = projectWidth + 1; // Avoid division by zero.
var percent = Math.floor(100.0 * percentBBox.width / projectWidth);
if (percent > 100.0) percent = 100;
if (percent < 0) percent = 0;
projectModel.set('percent_completed', ""+percent); // Write to project model and update tree via events
me.needsRedraw = true; // redraw the entire Gantt editor surface.
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onProjectPercentResize: Finished');
},
/**
* Create a dependency between two two tasks.
* This function is called by onMouseUp as a successful
* "drop" action if the drop target is another project.
*/
onCreateDependency: function(fromSprite, toSprite) {
var me = this;
var fromTaskModel = fromSprite.dndConfig.model;
var toTaskModel = toSprite.dndConfig.model;
if (null == fromTaskModel) return;
if (null == toTaskModel) return;
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.onCreateDependency: Starting: '+fromTaskModel.get('id')+' -> '+toTaskModel.get('id'));
// Try connecting the two tasks via a task dependency
var fromTaskId = fromTaskModel.get('id'); // String value!
if (null == fromTaskId) return; // Something went wrong...
var toTaskId = toTaskModel.get('id'); // String value!
if (null == toTaskId) return; // Something went wrong...
if (fromTaskId == toTaskId) return; // No dependency on itself...
// Create a new dependency object
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.createDependency: '+fromTaskId+' -> '+toTaskId);
var dependency = {
pred_id: parseInt(fromTaskId),
succ_id: parseInt(toTaskId),
type_id: 9662, // "End-to-Start", please see im_categories.category_id
diff: 0.0
};
var dependencies = toTaskModel.get('predecessors');
if ("" == dependencies) { dependencies = []; }
dependencies.push(dependency);
toTaskModel.set('predecessors', dependencies);
// Check for cyclic dependencies with new dependency
var cyclicP = me.ganttSchedulingController.checkCyclicDependencies(false); // Check for cyclic dependencies
if (cyclicP) {
// Found cyclic structure: Delete the new dependency again
me.ganttSchedulingController.checkCyclicDependenciesDelete(true, fromTaskId, toTaskId);
me.ganttSchedulingController.checkCyclicDependencies(true); // Fix internal vars
alert('onCreateDependency: You are not allowed to create a cyclic dependency');
}
toTaskModel.setDirty(); // above just modified array, so we need to notify
me.needsRedraw = true;
me.ganttSchedulingController.onCreateDependency(dependency);
if (me.debug) console.log('GanttEditor.view.GanttBarPanel.onCreateDependency: Finished');
},
/**
* Draw all Gantt bars
*/
redraw: function(a, b, c, d, e) {
var me = this;
if (me.debug) console.log('PO.class.GanttDrawComponent.redraw: Starting');
if (!me.needsRedraw) { return; } // Lazy redraw: set only if a redraw is really necessary
if (undefined === me.surface) { return; } // Don't redraw when still initializing
var sched = me.ganttSchedulingController;
if (!!sched) {
sched.onRedraw(me); // Cleanup cyclic dependencies
}
// Get the root of the ganttTree
var ganttTreeView = me.objectPanel.getView();
var rootNode = me.objectStore.getRootNode();
var numNodes = me.nodesInTree(rootNode);
var surfaceYSize = numNodes * 20 + 50; // +50 offset for the column bar on in TreePanel
if (me.debug) console.log('PO.class.GanttDrawComponent.redraw: numNodes='+numNodes);
var panelWidth = me.getBox().width;
if (panelWidth > me.axisEndX) me.axisEndX = panelWidth; // Resize the surface after a user panel resize
me.surface.removeAll();
me.surface.setSize(me.axisEndX, surfaceYSize); // Set the size of the drawing area
me.drawAxisAuto(); // Draw the top axis
if (me.scrollX) { // Deferred scrolling - only here we've got a scrollableEl...
var scrollableEl = me.getEl(); // Ext.dom.Element that enables scrolling
scrollableEl.setScrollLeft(me.scrollX);
delete me.scrollX; // Remove the attribute - scroll only once...
}
// Calculate the per-user hash of assignments of a user to other tasks
me.setupOverassignments();
// Iterate through all children of the root node and check if they are visible
rootNode.cascadeBy(function(model) {
var viewNode = ganttTreeView.getNode(model);
if (viewNode == null) { return; } // Hidden nodes have no viewNode -> no bar
me.drawProjectBar(model);
});
// Iterate through all children and draw dependencies
rootNode.cascadeBy(function(model) {
var viewNode = ganttTreeView.getNode(model);
if (viewNode == null) { return; } // Hidden nodes have no viewNode -> no bar
me.drawProjectDependencies(model);
});
if (me.debug) console.log('PO.class.GanttDrawComponent.redraw: Finished');
},
/**
* Compile overassignment information in a hash of hashes
* in order to speed-up lookup during redraw per task
*/
setupOverassignments() {
var me = this;
var drawOverassignments = me.preferenceStore.getPreferenceBoolean('show_project_cross_project_overassignments', true);
if (!drawOverassignments) return;
if (me.debug) { if (me.debug) console.log('PO.view.gantt.GanttBarPanel.setupOverassignments: Starting'); }
var absenceAssignmentStore = Ext.StoreManager.get('absenceAssignmentStore');
var projectMemberStore = Ext.StoreManager.get('projectMemberStore');
var overHash = {};
absenceAssignmentStore.each(function(absence) {
var userId = parseInt(absence.get('user_id'));
var objectId = parseInt(absence.get('object_id'));
var objectName = absence.get('name');
var startDate = absence.get('start_date').substring(0,10);
var endDate = absence.get('end_date').substring(0,10);
var startX = me.date2x(new Date(startDate)); if (!startX) return;
var endX = me.date2x(new Date(endDate)); if (!endX) return;
var type = absence.get('object_type');
var user_id = absence.get('user_id');
var user_name = absence.get('user_name');
var context_id = absence.get('context_id');
var context = absence.get('context');
// Write to hash
var userHash = overHash[userId];
if (!userHash) {
userHash = {};
overHash[userId] = userHash;
}
userHash[objectId] = {startX: startX, endX: endX, name: objectName, type: type, userName: user_name, contextId: context_id, context: context};
});
// Iterate through all children of the root node and check if they are visible
var rootNode = me.objectStore.getRootNode();
rootNode.cascadeBy(function(task) {
// Skip summary activities (tasks with children)
var children = task.childNodes;
if (Array.isArray(children)) {
var numChildren = children.length;
if (numChildren > 0) return;
}
var task_id = task.get('id');
var taskId = parseInt(task_id); if (!taskId) return;
var taskName = task.get('project_name');
var assignees = task.get('assignees'); // Array of {id, percent, name, email, initials}
var startDate, endDate;
// Get start- and end date (look at parents if necessary...)
var p = task;
while ("" == (startDate = p.get('start_date')) && !!p.parentNode) { p = p.parentNode;}
var p = task;
while ("" == (endDate = p.get('end_date')) && !!p.parentNode) { p = p.parentNode; }
if ("" == startDate || "" == endDate) { return; }
var startDate = PO.Utilities.pgToDate(startDate);
var endDate = PO.Utilities.pgToDate(endDate);
if (!startDate) return; // skip if invalid for some reason...
if (!endDate) return;
var startTime = startDate.getTime();
var endTime = endDate.getTime();
var startX = me.date2x(startTime); // X position based on time scale
var endX = me.date2x(endTime); // X position based on time scale
assignees.forEach(function(assignee) {
if (0 == assignee.percent) { return; } // Don't show empty assignments
var userId = assignee.user_id;
var userModel = projectMemberStore.getById(""+assignee.user_id);
if (!userModel) return;
var userName = userModel.get('name');
var userHash = overHash[userId]; // Write to hash
if (!userHash) {
userHash = {};
overHash[userId] = userHash;
}
userHash[taskId] = {startX: startX, endX: endX, name: taskName, type: 'im_project', userName: userName, contextId: taskId, context: taskName};
});
});
me.overassignmentHash = overHash;
if (me.debug) { if (me.debug) console.log('PO.view.gantt.GanttBarPanel.setupOverassignments: Finished'); }
},
/**
* Delete bar from surface
*/
undrawProjectBar: function(project) {
var me = this;
var surface = me.surface;
},
/**
* Draw a single bar for a project or task
*/
drawProjectBar: function(project) {
var me = this;
// if (me.debug) { if (me.debug) console.log('PO.view.gantt.GanttBarPanel.drawProjectBar: Starting'); }
var surface = me.surface;
var taskName = project.get('project_name');
var percentCompleted = parseFloat(project.get('percent_completed')); if (isNaN(percentCompleted)) percentCompleted = 0.0;
var predecessors = project.get('predecessors');
var assignees = project.get('assignees'); // Array of {id, percent, name, email, initials}
var baselines = project.get('baselines'); // Array of {id, percent, name, email, initials}
var absenceAssignmentStore = Ext.StoreManager.get('absenceAssignmentStore');
var plannedHours = parseFloat(project.get('planned_units')); if (isNaN(plannedHours)) plannedHours = 0.0;
var loggedHours = parseFloat(project.get('reported_hours_cache')); if (isNaN(loggedHours)) loggedHours = 0.0;
var percentLogged = 0; if (plannedHours > 0) percentLogged = 100.0 * loggedHours / plannedHours;
var workDone = plannedHours * percentCompleted / 100.0;
var startDate, endDate;
// Get start- and end date (look at parents if necessary...)
var p = project;
while ("" == (startDate = p.get('start_date')) && !!p.parentNode) { p = p.parentNode;}
var p = project;
while ("" == (endDate = p.get('end_date')) && !!p.parentNode) { p = p.parentNode; }
if ("" == startDate || "" == endDate) { return; }
var startDate = PO.Utilities.pgToDate(startDate);
var endDate = PO.Utilities.pgToDate(endDate);
if (!startDate) return; // skip if invalid for some reason...
if (!endDate) return;
var startTime = startDate.getTime();
var endTime = endDate.getTime();
var x = me.date2x(startTime); // X position based on time scale
var y = me.calcGanttBarYPosition(project); // Y position based on TreePanel y position of task.
var w = Math.floor(me.axisEndX * (endTime - startTime) / (me.axisEndDate.getTime() - me.axisStartDate.getTime()));
if (w < 2) { w = 2; } // Start/end are completely wrong (probably end < start..)
var h = me.ganttBarHeight; // Constant determines height of the bar
var d = Math.floor(h / 2.0) + 1; // Size of the indent of the super-project bar
// Store the start and end points of the Gantt bar
var id = project.get('id');
me.taskBBoxHash[id] = {x: x, y: y, width: w, height: h}; // Remember the outer dimensions of box for dependencies
me.taskModelHash[id] = project; // Remember the models per ID
var drawn = null;
// ---------------------------------------------------------------
// Task with zero length: Draw a milestone
if (!drawn && project.isMilestone()) { // either explicitely marked or zero duration
drawn = "milestone";
var m = h/2; // Half the size of the bar height
var spriteBar = surface.add({
type: 'path',
stroke: 'black',
'stroke-width': 0.3,
fill: 'black',
zIndex: 0,
path: 'M '+ (x-m) + ', ' + (y+m)
+ 'L '+ (x) + ', ' + (y)
+ 'L '+ (x+m) + ', ' + (y+m)
+ 'L '+ (x) + ', ' + (y+h)
+ 'L '+ (x-m) + ', ' + (y+m),
listeners: { // Highlight the sprite on mouse-over
mouseover: function() { this.animate({duration: 500, to: {'stroke-width': 2.0}}); },
mouseout: function() { this.animate({duration: 500, to: {'stroke-width': 0.3}}); }
}
}).show(true);
}
// ---------------------------------------------------------------
// Draw a standard Gantt bar if the task is a leaf (has no children)
if (!drawn && !project.hasChildNodes()) { // Parent tasks don't have DnD and look different
drawn = "bar";
// The main Gantt bar with Drag-and-Drop configuration
var spriteBar = surface.add({
type: 'rect', x: x, y: y, width: w, height: h, radius: 3,
fill: 'url(#gradientId)',
stroke: 'blue',
'stroke-width': 0.3,
zIndex: -100, // Neutral zIndex - in the middle
listeners: { // Highlight the sprite on mouse-over
mouseover: function() { this.animate({duration: 500, to: {'stroke-width': 0.5}}); },
mouseout: function() { this.animate({duration: 500, to: {'stroke-width': 0.3}}); }
}
}).show(true);
// Resize-Handle of the Gantt Bar: This is an invisible box at the right end of the bar
// used to change the cursor and to initiate a specific resizing DnD operation.
var spriteBarHandle = surface.add({
type: 'rect', x: x+w+2, y: y, width: 3, height: h, // Located at the right edge of spriteBar.
stroke: 'red', // For debugging - not visible
fill: 'red', // Need to be filled for cursor display
opacity: 0, // Invisible
zIndex: 50, // At the very top of the z-stack
style: { cursor: 'e-resize' } // Shows a horizontal arrow cursor
}).show(true);
spriteBarHandle.dndConfig = {
model: project, // Store the task information for the sprite
baseSprite: spriteBar,
dragAction: function(panel, e, diff, dndConfig) {
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.drawProjectBar.spriteBarHandle.dragAction:');
var baseBBox = panel.dndBaseSprite.getBBox();
var shadow = panel.dndShadowSprite;
var width = baseBBox.width + diff[0];
if (width < 0) width = 0;
shadow.setAttributes({width: width}).show(true);
},
dropAction: function(panel, e, diff, dndConfig) {
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.drawProjectBar.spriteBarHandle.dropAction:');
me.onProjectResize(panel.dndBaseSprite, diff[0]); // Changing end-date to match x coo
}
};
var tooltipBaseHtml = ""+
""+
"work planned: | "+plannedHours+" hours |
"+
"work done: | "+workDone+" hours | = "+percentCompleted+"% |
"+
"hours logged: | "+loggedHours+" hours | = "+percentLogged+"% |
"+
"
";
// Percent_complete bar on top of the Gantt bar:
// Allows for special DnD affecting only %done.
var drawPercentCompleted = me.preferenceStore.getPreferenceBoolean('show_percent_done_bar', true);
if (drawPercentCompleted) {
var opacity = 0.0;
if (isNaN(percentCompleted)) percentCompleted = 0;
if (percentCompleted > 0.0) opacity = 1.0;
var percentW = w*percentCompleted/100;
if (percentW < 2) percentW = 2;
var spriteBarPercent = surface.add({
type: 'rect', x: x, y: y+2, width: percentW, height: (h-6)/2,
stroke: 'black',
fill: 'black',
'stroke-width': 0.0,
zIndex: 20,
opacity: opacity
}).show(true);
var tooltipHtml = "Work Done: "+percentCompleted+"%"+tooltipBaseHtml;
Ext.create("Ext.tip.ToolTip", { target: spriteBarPercent.el, width: 300, html: tooltipHtml, hideDelay: 5000 });
} // end drawPercentCompleted
// Percent_complete bar on top of the Gantt bar:
// Allows for special DnD affecting only %done.
var drawLoggedHoursBar = me.preferenceStore.getPreferenceBoolean('show_logged_hours_bar', true);
if (drawLoggedHoursBar) {
var opacity = 0.0;
if (percentLogged > 0.0) opacity = 1.0;
var percentW = w*percentLogged/100;
if (percentW < 2) percentW = 2;
var color = 'blue';
if (percentLogged > percentCompleted)
color = 'red';
var spriteBarLoggedHours = surface.add({
type: 'rect', x: x, y: y+2+(h-6)/2+2, width: percentW, height: (h-6)/2,
stroke: color,
fill: color,
'stroke-width': 0.0,
zIndex: 20,
opacity: opacity
}).show(true);
var tooltipHtml = "Hours Logged: "+loggedHours+" hours"+tooltipBaseHtml;
Ext.create("Ext.tip.ToolTip", { target: spriteBarLoggedHours.el, width: 300, html: tooltipHtml, hideDelay: 5000 });
}
}
// ---------------------------------------------------------------
// Draw a Gantt container task if the task has children
if (!drawn && project.hasChildNodes()) { // Parent tasks don't have DnD and look different
drawn = "supertask";
var spriteBar = surface.add({
type: 'path',
stroke: 'blue',
'stroke-width': 0.3,
fill: 'url(#gradientId)',
zIndex: 0,
path: 'M '+ x + ', ' + y
+ 'L '+ (x+w) + ', ' + (y)
+ 'L '+ (x+w) + ', ' + (y+h)
+ 'L '+ (x+w-d) + ', ' + (y+h-d)
+ 'L '+ (x+d) + ', ' + (y+h-d)
+ 'L '+ (x) + ', ' + (y+h)
+ 'L '+ (x) + ', ' + (y),
listeners: { // Highlight the sprite on mouse-over
mouseover: function() { this.animate({duration: 500, to: {'stroke-width': 2.0}}); },
mouseout: function() { this.animate({duration: 500, to: {'stroke-width': 0.3}}); }
}
}).show(true);
}
if (!drawn) { alert('GanttBarPanel.drawProjectBar: not drawn for some reason'); }
// ---------------------------------------------------------------
// Draw baselines
var drawBaseline = me.preferenceStore.getPreference('show_project_baseline', false);
console.log('PO.view.gantt.GanttBarPanel.drawProjectBar.baselines: drawBaseline='+drawBaseline);
if (drawBaseline) {
if (baselines && "" != baselines) {
var baseline = baselines[drawBaseline];
if (baseline && "" != baseline) {
var baselineStart = baseline.start_date;
var baselineEnd = baseline.end_date;
if (!!baselineStart && !!baselineEnd) {
var baselineStartDate = PO.Utilities.pgToDate(baselineStart);
var baselineEndDate = PO.Utilities.pgToDate(baselineEnd);
var startX = me.date2x(baselineStartDate); // X position based on time scale
var endX = me.date2x(baselineEndDate); // X position based on time scale
// A baseline shadow
var baselineSpriteBar = surface.add({
type: 'rect', x: startX, y: y, width: endX - startX, height: h, radius: 3,
stroke: 'red',
'stroke-width': 0.3,
zIndex: -200, // In the background
}).show(true);
// 2021-08-24 fraber: Tooltips don't show because the stroke is so thin.
// Ext.create("Ext.tip.ToolTip", { target: spriteBar.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
}
}
}
}
// ---------------------------------------------------------------
// Draw assignee initials behind the Gantt bar.
var drawAssignees = me.preferenceStore.getPreferenceBoolean('show_project_assigned_resources', true);
if (drawAssignees) {
var projectMemberStore = Ext.StoreManager.get('projectMemberStore');
var text = "";
if ("" != assignees) {
assignees.forEach(function(assignee) {
if (0 == assignee.percent) { return; } // Don't show empty assignments
var userModel = projectMemberStore.getById(""+assignee.user_id);
if (!userModel) return;
if ("" != text) { text = text + ', '; }
text = text + userModel.get('first_names').substr(0, 1) + userModel.get('last_name').substr(0, 1);
if (100 != assignee.percent) {
text = text + '['+assignee.percent+'%]';
}
});
var xOffset = w + 4; // Default: Start directly behind the bar
switch (drawn) {
case 'milestone': xOffset = 8; // Milestone: Ignore bar width, but add some extra space
}
var axisText = surface.add({type:'text', text:text, x:x+xOffset, y:y+d, fill:'#000', font:"10px Arial"}).show(true);
}
}
// ---------------------------------------------------------------
// Draw indication of user absence or assignment
var drawOverassignments = me.preferenceStore.getPreferenceBoolean('show_project_cross_project_overassignments', true);
if (drawOverassignments && !project.isMilestone() && !project.hasChildNodes()) {
assignees.forEach(function(assigneeModel) {
var assigneeId = assigneeModel.user_id;
var assigneeHash = me.overassignmentHash[assigneeId];
if (!assigneeHash) return;
for (var object_id in assigneeHash) {
var objectId = parseInt(object_id);
if (objectId == id) continue; // Don't show overallocation with the task itself :-)
var absenceHash = assigneeHash[objectId];
var startX = absenceHash.startX;
var endX = absenceHash.endX;
// Skip if not overlapping with Gantt bar
if (startX > x + w) continue; // starts after the end of the Gantt bar
if (endX < x) continue // ends before the start of the Gantt bar
// Limit red bar to the Gantt bar size
if (startX < x) { startX = x; } // starts before Gantt bar => set to start of Gantt bar
if (endX > x + w) { endX = x + w; } // ends after Gantt bar end => set end to end of Gantt bar
if (startX < 0) { startX = 1; }
var width = endX - startX;
if (width < 1) { width = 1; }
var assigneeBar = surface.add({
type: 'rect', x: startX, y: y-4, width: width, height: h+8, radius: 0,
fill: 'red',
opacity: 0.1,
'stroke-width': 1,
zIndex: -1000 // Neutral zIndex - in the middle
}).show(true);
var type = absenceHash.type;
var name = absenceHash.name;
var absenceHtml = "";
var user_name = absenceHash.userName;
var context_id = absenceHash.contextId;
var context = absenceHash.context;
switch (type) {
case "im_project":
absenceHtml = 'Overassignment
'+
'User: ' + user_name + '
' +
'has already been assigned
' +
'to Task: ' + name + '
' +
'of Project: ' + context + '';
break;
case "im_user_absence":
absenceHtml = 'Assignment during absence
'+
'User: ' + user_name + '
' +
'has been assigned while absent due to
' +
'Absence: ' + name + '
';
break;
default:
alert('GanttBarPanel.drawProjectBar: Found unknown Vacation or Absence type: '+type);
}
Ext.create("Ext.tip.ToolTip", {
target: assigneeBar.el,
width: 250,
html: absenceHtml,
hideDelay: 2000
});
}
});
}
// ---------------------------------------------------------------
// Draw financial documents above Gantt bar
var drawFinDocs = me.preferenceStore.getPreferenceBoolean('show_project_findocs', true);
if (drawFinDocs) {
var invoices = project.get('invoices'); // Array of {id, cost_name, cost_type_id, cost_type}
if (!!invoices && invoices instanceof Array && invoices.length > 0) {
var imageWidth = 19;
var busyX = {};
invoices.forEach(function(invoice) {
var prefix = invoice.cost_type.substring(0,1).toLowerCase();
var effectiveDate = new Date(invoice.effective_date);
var invoiceX = me.date2x(effectiveDate);
if (!invoiceX) return;
// Make sure multiple invoices appear beside each other
var pos = Math.round(invoiceX / imageWidth);
while (pos in busyX) {
invoiceX = invoiceX + imageWidth;
var pos = Math.round(invoiceX / imageWidth);
}
busyX[pos] = pos; // mark as busy
var invoiceBar = surface.add({
type: 'image', x: invoiceX, y: y-h, width: imageWidth, height: 13,
src: "/intranet/images/"+prefix+".gif",
listeners: { mousedown: function() { window.open('/intranet-invoices/view?invoice_id='+invoice.id); } }
}).show(true);
});
}
}
// Add a drag-and-drop configuration to all spriteBars (bar, supertask and milestone)
// in order to allow them to act as both source and target of inter-task dependencies.
spriteBar.dndConfig = { // Drag-and-drop configuration
model: project, // Store the task information for the sprite
baseSprite: spriteBar, // "Base" sprite for the DnD action
dragAction: function(panel, e, diff, dndConfig) { // Executed onMouseMove in AbstractGanttPanel
var shadow = panel.dndShadowSprite; // Sprite "shadow" (copy of baseSprite) to move around
var linkSprite = panel.dndLinkSprite;
if ( diff[1] > 10 || diff[1] < -10 ) {
shadow.hide(true);
linkSprite.show(true);
var point = me.getMousePoint(e);
linkSprite.setAttributes( {x: point[0], y: point[1] - 5}, true);
} else {
shadow.show(true);
shadow.setAttributes({translate: {x: diff[0], y: 0}}, true); // Move shadow according to mouse position
linkSprite.hide(true);
};
},
dropAction: function(panel, e, diff, dndConfig) { // Executed onMouseUp in AbastractGanttPanel
if (me.debug) console.log('PO.view.gantt.GanttBarPanel.drawProjectBar.spriteBar.dropAction:');
panel.dndLinkSprite.destroy(); // Hide Link graphic
var point = me.getMousePoint(e); // Corrected mouse coordinates
var baseSprite = panel.dndBaseSprite; // spriteBar to be affected by DnD
if (!baseSprite) { return; } // Something went completely wrong...
var dropSprite = panel.getSpriteForPoint(point); // Check where the user has dropped the shadow
if (baseSprite == dropSprite) { dropSprite = null; } // Dropped on same sprite? => normal drop
if (0 == Math.abs(diff[0]) + Math.abs(diff[1])) { // Same point as before?
return; // Drag-start == drag-end or single-click
}
if (null != dropSprite) {
me.onCreateDependency(baseSprite, dropSprite); // Dropped on another sprite - create dependency
} else {
me.onProjectMove(baseSprite, diff[0]); // Dropped on empty space or on the same bar
}
}
};
spriteBar.mode = project;
// if (me.debug) { if (me.debug) console.log('PO.view.gantt.GanttBarPanel.drawProjectBar: Finished'); }
},
/**
* Iterate throught all successors of a Gantt bar
* and draw dependencies.
*/
drawProjectDependencies: function(project) {
var me = this;
var predecessors = project.get('predecessors');
if (!predecessors instanceof Array) return;
if (!me.preferenceStore.getPreferenceBoolean('show_project_dependencies', true)) return;
for (var i = 0, len = predecessors.length; i < len; i++) {
var dependencyModel = predecessors[i];
me.drawDependency(dependencyModel); // Draw a dependency arrow between Gantt bars
}
},
/**
* Draws a dependency line from one bar to the next one
*/
drawDependency: function(dependencyModel) {
var me = this;
// if (me.debug) console.log('GanttEditor.view.GanttBarPanel.drawTaskDependency: Starting');
var fromId = dependencyModel.pred_id;
var fromModel = me.taskModelHash[fromId]
var toId = dependencyModel.succ_id;
var toModel = me.taskModelHash[toId]
// We can get dependencies from other projects.
// These are not in the taskModelHash, so just skip these
// ToDo: Show dependencies from other projects
if (undefined === fromModel || undefined === toModel) {
if (me.debug) console.log('GanttEditor.view.GanttBarPanel.drawTaskDependency: Dependency from other project: Skipping');
return;
}
var depName = 'Task dependency';
var depTypeId = dependencyModel.type_id;
if (depTypeId == 9650) depTypeId = 9662; // compatibility
switch (depTypeId) {
case 9660: depName = '@finish_to_finish_l10n@'; break;
case 9662: depName = '@finish_to_start_l10n@'; break;
case 9664: depName = '@start_to_finish_l10n@'; break;
case 9666: depName = '@start_to_start_l10n@'; break;
default:
alert('drawDependency: found undefined dependencyTypeId='+depTypeId);
return;
}
// Text for dependency tool tip
var html = ""+depName+":
" +
"From " + fromModel.get('project_name') + " " +
"to " + toModel.get('project_name') + "";
me.drawDependencyMsp(dependencyModel,html);
// if (me.debug) console.log('GanttEditor.view.GanttBarPanel.drawTaskDependency: Finished');
},
/**
* Draws a dependency line from one bar to the next one.
*
* ToDo: Also draw start-end-start and other dependency types apart from end-to-start
*/
drawDependencyMsp: function(dependencyModel, tooltipHtml) {
var me = this;
var s, color, startX, startY, endX, endY;
var objectPanelView = me.objectPanel.getView(); // The "view" for the GridPanel with HTML elements
var fromId = dependencyModel.pred_id;
var fromBBox = me.taskBBoxHash[fromId]; // We start drawing with the end of the first bar...
var fromModel = me.taskModelHash[fromId]
var toId = dependencyModel.succ_id;
var toBBox = me.taskBBoxHash[toId]; // .. and draw towards the start of the 2nd bar.
var toModel = me.taskModelHash[toId]
if (!fromBBox || !toBBox) { return; }
// Double check for nodes that are in the cache, but that have just been hidden
// ToDo: Delete nodes from the cache when hiding branches in the tree
var fromNode = objectPanelView.getNode(fromModel);
var toNode = objectPanelView.getNode(toModel);
if (!fromNode || !toNode) { return; }
s = me.arrowheadSize;
startY = fromBBox.y + fromBBox.height/2; // vertical start point is always the middle of the bar
var depTypeId = dependencyModel.type_id;
if (depTypeId == 9650) depTypeId = 9662; // compatibility: Depends -> Finish-to-Start
switch (depTypeId) {
case 9660:
// finish_to_finish - draw from pred right center to the right, down to succ and left back to the right side of the bar.
startX = fromBBox.x + fromBBox.width; // Right side of the fromBBox
endX = toBBox.x + toBBox.width; // right of the toBBox
endY = toBBox.y + toBBox.height/2; // End at the center of the toBBox
color = '#222'; // dark grey color
// Go from startX to the left, then down and then back to the right to endX
var rightMaxX = Math.max(startX, endX) + fromBBox.height;
var arrowLine = me.surface.add({
type: 'path',
stroke: color,
'shape-rendering': 'crispy-edges',
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (startX) + ', ' + (startY)
+ 'L '+ (rightMaxX) + ', ' + (startY)
+ 'L '+ (rightMaxX) + ', ' + (endY)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowLine.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowLine.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
// Draw the arrow head (filled)
var arrowHead = me.surface.add({
type: 'path',
stroke: color,
fill: color,
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (endX) + ', ' + (endY) // Point of arrow head
+ 'L '+ (endX+s) + ', ' + (endY-s)
+ 'L '+ (endX+s) + ', ' + (endY+s)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowHead.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowHead.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
break;
case 9662:
// finish_to_start - start off at the center right of the task and point down on the top of the successor
startX = fromBBox.x + fromBBox.width; // End-to-start dep starts at the right side of the fromBBox
color = '#222'; // dark grey color
if (toBBox.x < fromBBox.x + fromBBox.width) { // Inverse dep starts at the left side of the fromBBox
color = 'red';
startX = fromBBox.x;
}
endX = toBBox.x + s; // Slightly to the right of the start of the task
if (toModel.isMilestone()) { endX = toBBox.x; } // Point directly to the start of the milestone
if (toBBox.y >= fromBBox.y + fromBBox.height) { // "normal" dependency from a task higher up to a task further down
endY = toBBox.y;
} else { // "inverse" dependency from a lower task to a task higher up
endY = toBBox.y + toBBox.height;
s = -s; // Draw the dependency arrow from bottom to top
}
// Draw the main connection line between start and end.
var arrowLine = me.surface.add({
type: 'path',
stroke: color,
'shape-rendering': 'crispy-edges',
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (startX) + ', ' + (startY)
+ 'L '+ (endX) + ', ' + (startY)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowLine.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowLine.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
// Draw the arrow head (filled)
var arrowHead = me.surface.add({
type: 'path',
stroke: color,
fill: color,
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (endX) + ', ' + (endY) // Point of arrow head
+ 'L '+ (endX-s) + ', ' + (endY-s)
+ 'L '+ (endX+s) + ', ' + (endY-s)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowHead.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowHead.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
break;
case 9664:
// start_to_finish
startX = fromBBox.x; // Right side of the fromBBox
endX = toBBox.x + toBBox.width; // right of the toBBox
endY = toBBox.y + toBBox.height/2; // End at the center of the toBBox
color = '#222'; // dark grey color
// Go from startX to the left, then down and then back to the right to endX
var arrowLine = me.surface.add({
type: 'path',
stroke: color,
'shape-rendering': 'crispy-edges',
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (startX) + ', ' + (startY)
+ 'L '+ (startX - toBBox.height) + ', ' + (startY)
+ 'L '+ (startX - toBBox.height) + ', ' + ((startY+endY)/2)
+ 'L '+ (endX + toBBox.height) + ', ' + ((startY+endY)/2)
+ 'L '+ (endX + toBBox.height) + ', ' + (endY)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowLine.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowLine.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
// Draw the arrow head (filled)
var arrowHead = me.surface.add({
type: 'path',
stroke: color,
fill: color,
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (endX) + ', ' + (endY) // Point of arrow head
+ 'L '+ (endX+s) + ', ' + (endY-s)
+ 'L '+ (endX+s) + ', ' + (endY+s)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowHead.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowHead.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
break;
case 9666:
// start_to_start - from left center of the pred bar to the left center of the succ
startX = fromBBox.x; // start at the left side of the fromBBox
endX = toBBox.x; // left of the toBBox
endY = toBBox.y + toBBox.height/2; // start at the left side of the fromBBox
color = '#222'; // dark grey color
// Go from startX to the left, then down and then back to the right to endX
var leftMinX = Math.min(startX, endX) - fromBBox.height;
var arrowLine = me.surface.add({
type: 'path',
stroke: color,
'shape-rendering': 'crispy-edges',
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (startX) + ', ' + (startY)
+ 'L '+ (leftMinX) + ', ' + (startY)
+ 'L '+ (leftMinX) + ', ' + (endY)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowLine.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowLine.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
// Draw the arrow head (filled)
var arrowHead = me.surface.add({
type: 'path',
stroke: color,
fill: color,
'stroke-width': 0.5,
zIndex: -100, // -100
path: 'M '+ (endX) + ', ' + (endY) // Point of arrow head
+ 'L '+ (endX-s) + ', ' + (endY-s)
+ 'L '+ (endX-s) + ', ' + (endY+s)
+ 'L '+ (endX) + ', ' + (endY)
}).show(true);
arrowHead.dependencyModel = dependencyModel;
Ext.create("Ext.tip.ToolTip", { target: arrowHead.el, width: 250, html: tooltipHtml, hideDelay: 2000 });
break;
default:
alert('drawDependencyMsp: found undefined dependencyTypeId='+depTypeId);
return;
}
if (me.debug) console.log('GanttEditor.view.GanttBarPanel.drawTaskDependency: Finished');
return;
}
});
intranet-gantt-editor-master/www/view/GanttBarPanel.tcl 0000664 0000000 0000000 00000001316 14412526641 0023564 0 ustar 00root root 0000000 0000000 # /packages/intranet-gantt-editor/www/view/GanttBarPanel.tcl
set finish_to_finish_l10n [lang::message::lookup "" intranet-gantt-editor.Finish_to_Finish "Finish-to-Finish"]
set finish_to_start_l10n [lang::message::lookup "" intranet-gantt-editor.Finish_to_Start "Finish-to-Start"]
set start_to_finish_l10n [lang::message::lookup "" intranet-gantt-editor.Start_to_Finish "Start-to-Finish"]
set start_to_start_l10n [lang::message::lookup "" intranet-gantt-editor.Start_to_Start "Start-to-Start"]
set edit_dependency_l10n [lang::message::lookup "" intranet-gantt-editor.Edit_Dependency "Edit Dependency"]
set delete_dependency_l10n [lang::message::lookup "" intranet-gantt-editor.Delete_Dependency "Delete Dependency"]
intranet-gantt-editor-master/www/view/GanttDependencyPropertyPanel.js 0000664 0000000 0000000 00000030073 14412526641 0026537 0 ustar 00root root 0000000 0000000 /*
* GanttDependencyPropertyPanel.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/*
* GanttTaskPropertyPanel.js
*
* Copyright (c) 2011 - 2014 ]project-open[ Business Solutions, S.L.
* This file may be used under the terms of the GNU General Public
* License version 3.0 or alternatively unter the terms of the ]po[
* FL or CL license as specified in www.project-open.com/en/license.
*/
/**
* A free floating singleton TabPanel with several elements
* allowing to edit the details of a single task.
*/
Ext.define('GanttEditor.view.GanttDependencyPropertyPanel', {
extend: 'Ext.Window',
id: 'ganttDependencyPropertyPanel',
alias: 'ganttDependencyPropertyPanel',
title: 'Dependency Properties',
id: 'ganttDependencyPropertyPanel',
senchaPreferenceStore: null,
debug: false,
width: 500,
height: 420,
closable: true,
closeAction: 'hide',
resizable: true,
modal: false,
layout: 'fit',
dependencyModel: null, // Set by setValue() before show()
initComponent: function() {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.initialize: Starting');
this.callParent(arguments);
var dependencyPropertyFormGeneral = Ext.create('Ext.form.Panel', {
title: 'General',
id: 'dependencyPropertyFormGeneral',
layout: 'anchor',
fieldDefaults: {
labelAlign: 'right',
labelWidth: 90,
msgTarget: 'qtip',
margins: '5 5 5 5',
},
items: [{
xtype: 'fieldset',
title: 'General',
defaultType: 'textfield',
layout: 'anchor',
items: [{
xtype: 'numberfield',
fieldLabel: 'Lag',
name: 'diff',
width: 200,
value: '0',
allowBlank: true
}, {
xtype: 'combobox',
fieldLabel: 'Lag Format',
name: 'diff_format_id',
displayField: 'category',
valueField: 'id',
queryMode: 'local',
typeAhead: true,
emptyText: 'Lag Format',
width: 250,
matchFieldWidth: false,
store: Ext.create('Ext.data.Store', {
fields: ['id', 'category'],
data : [
// {id: 9803, category: 'Month'},
// {id: 9804, category: 'e-Month'},
{id: 9805, category: 'Hour'},
// {id: 9806, category: 'e-Hour'},
{id: 9807, category: 'Day'},
// {id: 9808, category: 'e-Day'},
// {id: 9809, category: 'Week'},
// {id: 9810, category: 'e-Week'},
// {id: 9811, category: 'mo'},
// {id: 9812, category: 'emo'},
// {id: 9819, category: 'Percent'},
// {id: 9820, category: 'e-Percent'},
// {id: 9835, category: 'm?'},
// {id: 9836, category: 'em?'},
// {id: 9837, category: 'h?'},
// {id: 9838, category: 'eh?'},
// {id: 9839, category: 'd?'},
// {id: 9840, category: 'ed?'},
// {id: 9841, category: 'w?'},
// {id: 9842, category: 'ew?'},
// {id: 9843, category: 'mo?'},
// {id: 9844, category: 'emo?'},
// {id: 9851, category: 'Percent?'},
// {id: 9852, category: 'e-Percent?'},
]
}),
allowBlank: false,
forceSelection: true
}, {
xtype: 'combobox',
fieldLabel: 'Dependency Type',
name: 'type_id',
displayField: 'category',
valueField: 'id',
queryMode: 'local',
typeAhead: true,
emptyText: 'Dependency Type',
width: 250,
matchFieldWidth: false,
store: Ext.create('Ext.data.Store', {
fields: ['id', 'category'],
data : [
{id: 9660, category: "Finish-to-Finish"},
{id: 9662, category: "Finish-to-Start"},
{id: 9664, category: "Start-to-Finish"},
{id: 9666, category: "Start-to-Start"}
]
}),
allowBlank: false,
forceSelection: true
}]
}]
});
var dependencyPropertyTabpanel = Ext.create("Ext.tab.Panel", {
id: 'dependencyPropertyTabpanel',
border: false,
items: [
dependencyPropertyFormGeneral
],
buttons: [{
text: 'OK',
scope: me,
handler: me.onButtonOK
}, {
text: 'Delete',
scope: me,
handler: me.onButtonDelete
}, {
text: 'Cancel',
scope: me,
handler: me.onButtonCancel
}]
});
me.add(dependencyPropertyTabpanel);
// store panels in the main object
me.dependencyPropertyFormGeneral = dependencyPropertyFormGeneral;
me.dependencyPropertyTabpanel = dependencyPropertyTabpanel;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.initialize: Finished');
},
/**
* Get the format factor to convert dependency.diff (in seconds) to the format (for example: week)
*/
dependencyFormatFactor: function(format_id) {
var format_factor = 1.0;
if (!format_id) format_id = 0;
// Format the "diff" according to the most frequent formats
// LagFormat can be: 3=m, 4=em, 5=h, 6=eh, 7=d, 8=ed, 9=w, 10=ew,
// 11=mo, 12=emo, 19=%, 20=e%, 35=m?, 36=em?, 37=h?, 38=eh?, 39=d?,
// 40=ed?, 41=w?, 42=ew?, 43=mo?, 44=emo?, 51=%? and 52=e%?
switch (format_id) {
case 9803: format_factor = 30.0 * 24.0 * 3600.0; break; // m=month, has fixed 30 days of 8 hours each at the moment
case 9804: format_factor = 30.0 * 24.0 * 3600.0; break; // em=
case 9805: format_factor = 3600.0; break; // h=hour
case 9806: format_factor = 1.0; break; // eh=
case 9807: format_factor = 24.0 * 3600.0; break; // d=day
case 9808: format_factor = 24.0 * 3600.0; break; // ed=
case 9809: format_factor = 5.0 * 24.0 * 3600.0; break; // w=week, has 5 days
case 9810: format_factor = 1.0; break; // ew=
case 9811: format_factor = 30.0 * 24.0 * 3600.0; break; // mo=month?
case 9812: format_factor = 30.0 * 24.0 * 3600.0; break; // emo
case 9819: format_factor = 1.0; break; // %=Percent
case 9820: format_factor = 1.0; break; // e%=
case 9835: format_factor = 1.0; break; // e%=
case 9836: format_factor = 1.0; break; // e%=
case 9837: format_factor = 1.0; break; // e%=
case 9838: format_factor = 1.0; break; // e%=
case 9839: format_factor = 1.0; break; // e%=
case 9840: format_factor = 1.0; break; // e%=
case 9841: format_factor = 1.0; break; // e%=
case 9842: format_factor = 1.0; break; // e%=
case 9843: format_factor = 1.0; break; // e%=
case 9844: format_factor = 1.0; break; // e%=
case 9851: format_factor = 1.0; break; // e%=
case 9852: format_factor = 1.0; break; // e%=
case 0: break; // handles undefined case
default: alert('GanttDependencyPropertyPanel.setValue: Found invalid diff_format_id='+format_id);
}
return format_factor;
},
/**
* Save the modified form values into the model.
*/
onButtonOK: function(button, event) {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.onButtonOK');
var fields = me.dependencyPropertyFormGeneral.getValues(false, true, true, true); // get all fields into object
var dep = me.dependencyModel;
dep.type_id = fields.type_id;
dep.diff_format_id = fields.diff_format_id;
dep.diff = fields.diff;
// var formatFactor = me.dependencyFormatFactor(fields.diff_format_id); // 8 hours per day * 3600 seconds per hour...
// dep.diff = fields.diff * formatFactor;
// We need to force re-scheduling
me.ganttSchedulingController.schedule();
me.ganttBarPanel.needsRedraw = true;
me.hide(); // hide the DependencyProperty panel
},
/**
* Delete the dependency model.
*/
onButtonDelete: function(button, event) {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.onButtonDelete');
// ToDo: Delete dep
var dep = me.dependencyModel;
var predId = dep.pred_id;
var succModel = me.succProjectModel; // The successor activity that owns the dependency
// Remove dependency model from succModel
var predecessors = succModel.get('predecessors');
for (i = 0; i < predecessors.length; i++) {
var el = predecessors[i];
if (el.pred_id == predId) {
predecessors.splice(i,1);
}
}
succModel.set('predecessors',predecessors);
// We need to force re-scheduling
me.ganttSchedulingController.schedule();
me.ganttBarPanel.needsRedraw = true;
me.hide(); // hide the DependencyProperty panel
},
/**
* Simply hide the windows.
* This automatically discards any changes.
*/
onButtonCancel: function(button, event) {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.onButtonCancel');
me.hide(); // hide the DependencyProperty panel
},
setActiveTab: function(tab) {
var me = this;
me.dependencyPropertyTabpanel.setActiveTab(tab);
},
/**
* Try to hide the list of tabs and the outer frame
*/
hideTabs: function() {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.hideTabs: Starting');
var tabPanel = me.dependencyPropertyTabpanel;
var tabBar = tabPanel.tabBar;
tabBar.hide();
},
/**
* Show the properties of the specified dependency model.
* Write changes back to the dependency immediately (at the moment).
*/
setValue: function(dependency, succProjectModel) {
var me = this;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.setValue: Starting');
// Load the data into the various forms
var form = me.dependencyPropertyFormGeneral.getForm();
form.setValues(dependency);
var diff_format_id = dependency.diff_format_id;
var formatFactor = me.dependencyFormatFactor(diff_format_id); // 8 hours per day * 3600 seconds per hour...
var diff = dependency.diff;
// var correctedDiff = Math.round(100.0 * (diff / formatFactor)) / 100.0;
var diffField = form.findField('diff');
// diffField.setValue(correctedDiff);
diffField.setValue(diff);
me.dependencyModel = dependency; // Save the model for reference
me.succProjectModel = succProjectModel;
if (me.debug) console.log('GanttEditor.view.GanttDependencyPropertyPanel.setValue: Finished');
}
});