I must confess that I've been awfully tempted not to use workflow. There seems to be quite a learning curve, and it's much easier to duplicate the functionality of workflow doing it your own way. However, several programmers with much more experience than I insist that it is really the way to do things. So if you have a choice, they would recommend using workflow. I'll reserve judgement until I figure out enough to see if it was worth the effort or not.
-- -- packages/project-manager/sql/postgresql/project-manager-create.sql -- -- @author jade@bread.com -- @creation-date 2003-05-15 -- @cvs-id $Id: project-manager-create.sql,v 1.1 2003/05/15 21:52:31 oacs Exp $ -- -- \i project-manager-table-create.sql \i project-manager-functions-create.sql
-- TODO:
--
-- which items in this data model need to use the content repository?
-- need to add in workflow (for status among other things)
-- need to take into account acs-rels
-- add categories to projects
--
-- packages/project-manager/sql/postgresql/project-manager-table-create.sql
--
-- @author jader@bread.com and everyone else involved in this thread: http://ope
nacs.org/forums/message-view?message_id=90742
-- @creation-date 2003-05-15
--
create table pm_project (
project_id integer
constraint project_manager_id_fk
references acs_objects(object_id)
constraint pm_project_id_pk
primary key,
project_name varchar(255)
constraint pm_project_name_nn
not null,
-- a user-specified
project_code varchar(255),
-- for subprojects
parent_project_id integer
constraint pm_project_parent_project_id_fk
references pm_project,
goal varchar(4000),
description varchar(4000),
-- is the deadline computed from the end date, or from
-- today?
-- e = end, t = today
deadline_scheduling char(1) default 't'
constraint pm_project_dline_scheduling_ck
check (deadline_scheduling in ('t','e')),
planned_start_date timestamptz,
planned_end_date timestamptz,
actual_start_date timestamptz,
actual_end_date timestamptz,
ongoing_p char(1) default 'f'
constraint pm_project_ongoing_p_ck
check (ongoing_p in ('t','f'))
);
create function inline_0 ()
returns integer as '
begin
PERFORM acs_object_type__create_type (
''pm_project'', -- object_type
''Project'', -- pretty_name
''Projects'', -- pretty_plural
''acs_object'', -- supertype
''pm_project'', -- table_name
''project_id'', -- id_column
null, -- package_name
''f'', -- abstract_p
null, -- type_extension_table
''pm_project__name'' -- name_method
);
return 0;
end;' language 'plpgsql';
select inline_0 ();
drop function inline_0 ();
--
-- packages/project-manager/sql/postgresql/project-manager-functions-create.sql
--
-- @author jade@bread.com
-- @creation-date 2003-05-15
-- @cvs-id $Id: project-manager-functions-create.sql,v 1.1 2003/05/15 21:52:31 o
acs Exp $
--
--
select define_function_args('pm_project__new','project_id,project_name,project_c
ode,parent_project_id,goal,description,deadline_scheduling,planned_start_date,pl
anned_end_date,actual_start_date,actual_end_date,ongoing_p,creation_date,creatio
n_user,creation_ip,context_id');
create or replace function pm_project__new (integer,varchar,varchar,integer,varc
har,varchar,char(1),timestamptz,timestamptz,timestamptz,timestamptz,char(1),time
stamptz,integer,varchar,integer)
returns integer as '
declare
p_project_id alias for $1;
p_project_name alias for $2;
p_project_code alias for $3;
p_parent_project_id alias for $4;
p_goal alias for $5;
p_description alias for $6;
p_deadline_scheduling alias for $7;
p_planned_start_date alias for $8;
p_planned_end_date alias for $9;
p_actual_start_date alias for $10;
p_actual_end_date alias for $11;
p_ongoing_p alias for $12;
p_creation_date alias for $13;
p_creation_user alias for $14;
p_creation_ip alias for $15;
p_context_id alias for $16;
v_pm_project_id int;
begin
v_pm_project_id := acs_object__new (
p_project_id,
''pm_project'',
p_creation_date,
p_creation_user,
p_creation_ip,
p_context_id
);
insert into pm_project
(project_id,project_name,project_code,parent_project_id,goal,descripti
on,deadline_scheduling,planned_start_date,planned_end_date,actual_start_date,act
ual_end_date,ongoing_p)
values
(v_pm_project_id, p_project_name, p_project_code, p_parent_project_id,
p_goal, p_description, p_deadline_scheduling, p_planned_start_date, p_planned_e
nd_date, p_actual_start_date, p_actual_end_date, p_ongoing_p);
PERFORM acs_permission__grant_permission(
v_pm_project_id,
p_creation_user,
''admin''
);
return v_pm_project_id;
end;' language 'plpgsql';
/* The __delete function deletes a record and all related overhead. */
select define_function_args('pm_project___delete','project_id');
create or replace function pm_project__delete (integer)
returns integer as '
declare
p_pm_project_id alias for $1;
begin
delete from acs_permissions
where object_id = p_pm_project_id;
delete from pm_project
where project_id = p_pm_project_id;
raise NOTICE ''Deleting pm_project...'';
PERFORM acs_object__delete(p_pm_project_id);
return 0;
end;' language 'plpgsql';
/* When we created the acs object type above, we specified a
'name_method'. This is the name of a function that will return the
name of the object. This is a convention ensuring that all objects
can be identified. Now we have to build that function. In this case,
we'll return a field called title as the name. */
select define_function_args('pm_project___name','project_id');
create or replace function pm_project__name (integer)
returns varchar as '
declare
p_pm_project_id alias for $1;
v_pm_project_name pm_project.project_name%TYPE;
begin
select project_name into v_pm_project_name
from pm_project
where project_id = p_pm_project_id;
return v_pm_project_name;
end;
' language 'plpgsql';
-- packages/project-manager/sql/project-manager-drop.sql
-- drop script
--
-- @author jade@bread.com
-- @creation-date 2003-05-15
-- @cvs-id $Id: project-manager-drop.sql,v 1.1 2003/05/15 21:52:31 oacs Exp $
--
--drop package, which drops all functions created with define_function_args
select drop_package('pm_project');
--drop permissions
delete from acs_permissions where object_id in (select project_id from pm_projec
t);
--drop objects
create function inline_0 ()
returns integer as '
declare
object_rec record;
begin
for object_rec in select object_id from acs_objects where object_type=''
pm_project''
loop
perform acs_object__delete( object_rec.object_id );
end loop;
return 0;
end;' language 'plpgsql';
select inline_0();
drop function inline_0();
--drop table
drop table pm_project;
--drop type
select acs_object_type__drop_type(
'pm_project',
't'
);
ad_page_contract {
Main view page for projects.
@author jader@bread.com
@creation-date 2003-05-15
@cvs-id $Id: index.tcl,v 1.3 2003/05/16 22:23:58 oacs Exp $
@param orderby indicates when the user clicks on a column to order by that \
column
@return table_html preformatting html table constructed by querying the sam\
plenotes table
} {
{orderby:optional {title}}
} -properties {
table_html
}
# define the columns in the table
set table_def {
{edit "" {} {<td><a href="add-edit?project_id=$project_id">Edit</a></td>}}
{view "" {} {<td><a href="one?project_id=$project_id">View</a></td>}}
{project_name "Name"}
{project_code "Project code"}
{parent_project_id "Parent project"}
{goal "Goal"}
{description "Description"}
{planned_start_date "Start date (planned)"}
{planned_end_date "End date (planned)"}
{ongoing_p "Ongoing?"}
}
# construct an html table from the samplenotes database table
set table_html [ad_table -Torderby $orderby project_query {} $table_def]
<master> <property name="title">Projects</property> @table_html@ <p><a href="add-edit">Add a project</a></p>
<?xml version="1.0"?>
<queryset>
<fullquery name="project_query">
<querytext>
SELECT
project_id,
project_name,
project_code,
parent_project_id,
goal,
description,
deadline_scheduling,
to_char(planned_start_date,'YYYY MM DD') as planned_start_date,
to_char(planned_end_date,'YYYY MM DD') as planned_end_date,
ongoing_p
FROM
pm_project
[ad_order_by_from_sort_spec $orderby $table_def]
</querytext>
</fullquery>
</queryset>
ad_page_contract {
Simple add/edit form for projects
} {
project_id:integer,optional
{planned_start_date ""}
{planned_end_date ""}
} -properties {
context_bar:onevalue
title:onevalue
}
set user_id [ad_maybe_redirect_for_registration]
set package_id [ad_conn package_id]
if {[exists_and_not_null project_id]} {
set title "Edit a project"
set context_bar [ad_context_bar "Edit Project"]
} else {
set title "Add a project"
set context_bar [ad_context_bar "New Project"]
}
ad_form -name add_edit -form {
project_id:key
{project_name:text
{label "Project name"}
}
{project_code:text
{label "Project code"}
}
{parent_project_id:text(hidden)
{value ""}}
{goal:text(textarea)
{label "Project goal"}
{optional}
{html { rows 5 cols 40 wrap soft}}}
{description:text(textarea)
{label "Description"}
{optional}
{html { rows 5 cols 40 wrap soft}}}
{deadline_scheduling:text(select)
{label "Scheduling"}
{options {{"From today" "t"} {"From deadline" "e"}} {value $deadline_scheduling}} }
{planned_start_date:date {value {[util::date acquire clock [clock scan $planned_start_date]]}} optional
{label "Planned start date"}
{format "MONTH DD YYYY"}
{help}
}
{planned_end_date:date {value {[util::date acquire clock [clock scan $planned_end_date]]}} optional
{label "Planned end date"}
{format "MONTH DD YYYY"}
{help}
}
{ongoing_p:text(select)
{label "Project is ongoing?"}
{options {{"Yes" "t"} {"No" "f"}} {value $ongoing_p}} }
} -select_query_name project_query -on_submit {
set user_id [ad_conn user_id]
set peeraddr [ad_conn peeraddr]
} -new_data {
db_exec_plsql do_insert { *SQL* }
ad_returnredirect "."
ad_script_abort
} -edit_data {
db_dml do_update { *SQL* }
} -after_submit {
ad_returnredirect "index"
ad_script_abort
}
<master> <property name="context_bar">@context_bar@</property> <property name="title">@title@</property> <center> <formtemplate id="add_edit" style="plain-ibr"></formtemplate> </center>
<?xml version="1.0"?>
<queryset>
<fullquery name="do_insert">
<querytext>
select pm_project__new(
:project_id,
:project_name,
:project_code,
:parent_project_id,
:goal,
:description,
:deadline_scheduling,
to_timestamp(:planned_start_date,'YYYY MM DD HH24 MI SS'),
to_timestamp(:planned_end_date,'YYYY MM DD HH24 MI SS'),
null,
null,
:ongoing_p,
now(),
:user_id,
:peeraddr,
:package_id);
</querytext>
</fullquery>
<fullquery name="do_update">
<querytext>
UPDATE
pm_project
SET
project_name = :project_name,
project_code = :project_code,
parent_project_id = :parent_project_id,
goal = :goal,
description = :description,
deadline_scheduling = :deadline_scheduling,
planned_start_date = to_timestamp(:planned_start_date,'YYYY MM DD HH24 MI SS'),
planned_end_date = to_timestamp(:planned_end_date,'YYYY MM DD HH24 MI SS'),
ongoing_p = :ongoing_p
WHERE
project_id = :project_id
</querytext>
</fullquery>
<fullquery name="project_query">
<querytext>
select
project_id,
project_name,
project_code,
parent_project_id,
goal,
description,
deadline_scheduling,
to_char(planned_start_date,'YYYY MM DD') as planned_start_date,
to_char(planned_end_date,'YYYY MM DD') as planned_end_date,
ongoing_p
FROM
pm_project
where project_id = :project_id
</querytext>
</fullquery>
</queryset>
/tcl/project-procs.tcl. You'll need to copy a lot of the files from bug-tracker.
namespace eval project_manager::project {}
ad_proc -private project_manager::project::workflow_create {} {
Create the 'project' workflow for project-manager
} {
set spec {
project {
pretty_name "Project"
package_key "project-manager"
object_type "pm_project"
callbacks {
# leave these blank for now
# bug-tracker.FormatLogTitle
# bug-tracker.BugNotificationInfo
}
roles {
participant {
pretty_name "Participant"
callbacks {
# leave these blank for now
# workflow.Role_DefaultAssignees_CreationUser
}
}
manager {
pretty_name "Manager"
callbacks {
# leave these blank for now
# workflow.Role_DefaultAssignees_CreationUser
}
}
client_representative {
pretty_name "Client representative"
callbacks {
# bug-tracker.ComponentMaintainer
# bug-tracker.ProjectMaintainer
# workflow.Role_PickList_CurrentAssignees
# workflow.Role_AssigneeSubquery_RegisteredUsers
}
}
}
states {
open {
pretty_name "Open"
# hide_fields { resolution fixed_in_version }
}
closed {
pretty_name "Closed"
}
}
actions {
create {
pretty_name "Create"
pretty_past_tense "Created"
new_state "open"
initial_action_p t
}
edit {
pretty_name "Edit"
pretty_past_tense "Edited"
privileges { write }
always_enabled_p t
edit_fields {
# not sure what to do here
#component_id
#summary
#found_in_version
#role_assignee
#fix_for_version
#resolution
#fixed_in_version
}
}
close {
pretty_name "Close"
pretty_past_tense "Closed"
# what is this doing?
assigned_role "submitter"
assigned_states { resolved }
new_state "closed"
privileges { write }
}
reopen {
pretty_name "Reopen"
pretty_past_tense "Reopened"
enabled_states { resolved closed }
new_state "open"
privileges { write }
}
}
}
}
set workflow_id [workflow::fsm::new_from_spec -spec $spec]
return $workflow_id
}
So, how do you go about adding in the callbacks? Well, first of all, you better make sure you understand how acs-service-contracts work.
See my page on acs-service-contracts for a background on this. Don't skip this unless you already are comfortable with acs-service-contracts.
We add in an /tcl/install-procs.tcl file. I simply copied it over from bug-tracker, which is probably the best example to use for Workflow code.
The point I'm currently confused on is how to choose what service contracts to implement. Help?