Task assignment
Tasks can be assigned to users. An user may decide to "claim" a task directly, which will assign the task to him. Otherwise, it is possible to set some arbitrary user as the assignee or remove the assignee completely through the edition form. Marking a task as completed will remove the assignee, while re-activating a task will assign it to the user who re-activated it. Also, fixed a bug which allowed a completed task to be edited.
This commit is contained in:
parent
850d2fa8d4
commit
56741bccaa
12 changed files with 180 additions and 80 deletions
|
@ -38,8 +38,8 @@ class Dao_Users
|
|||
public function getUsers( )
|
||||
{
|
||||
return $this->query(
|
||||
'SELECT user_id , user_display_name , user_email '
|
||||
. 'FROM users '
|
||||
'SELECT user_id , user_display_name , user_email , user_view_name '
|
||||
. 'FROM users_view '
|
||||
. 'ORDER BY LOWER( user_email )' )->execute( );
|
||||
}
|
||||
|
||||
|
|
|
@ -76,12 +76,14 @@ class DAO_Tasks
|
|||
'SELECT t.task_id AS id, t.task_title AS title, t.item_id AS item ,'
|
||||
. 't.task_description AS description, t.task_added AS added_at, '
|
||||
. 'u1.user_view_name AS added_by, ct.completed_task_time AS completed_at, '
|
||||
. 'u2.user_view_name AS completed_by, t.user_id AS uid , '
|
||||
. 'u2.user_view_name AS assigned_to , u2.user_id AS assigned_id , '
|
||||
. 'u3.user_view_name AS completed_by, t.user_id AS uid , '
|
||||
. 't.task_priority AS priority '
|
||||
. 'FROM tasks t '
|
||||
. 'INNER JOIN users_view u1 ON u1.user_id = t.user_id '
|
||||
. 'LEFT OUTER JOIN completed_tasks ct ON ct.task_id = t.task_id '
|
||||
. 'LEFT OUTER JOIN users_view u2 ON u2.user_id = ct.user_id '
|
||||
. 'LEFT OUTER JOIN users_view u2 ON u2.user_id = t.user_id_assigned '
|
||||
. 'LEFT OUTER JOIN users_view u3 ON u3.user_id = ct.user_id '
|
||||
. 'WHERE t.task_id = $1' )->execute( $id );
|
||||
if ( empty( $result ) ) {
|
||||
return null;
|
||||
|
@ -182,10 +184,10 @@ class DAO_Tasks
|
|||
->execute( $task , $_SESSION[ 'uid' ] , $noteText );
|
||||
}
|
||||
|
||||
public function updateTask( $id , $item , $title , $priority , $description )
|
||||
public function updateTask( $id , $item , $title , $priority , $description , $assignee )
|
||||
{
|
||||
$result = $this->query( 'SELECT update_task( $1 , $2 , $3 , $4 , $5 ) AS error' )
|
||||
->execute( $id , $item , $title , $description , $priority );
|
||||
$result = $this->query( 'SELECT update_task( $1 , $2 , $3 , $4 , $5 , $6 ) AS error' )
|
||||
->execute( $id , $item , $title , $description , $priority , $assignee );
|
||||
return $result[0]->error;
|
||||
}
|
||||
|
||||
|
@ -236,4 +238,14 @@ class DAO_Tasks
|
|||
->execute( $from , $to );
|
||||
}
|
||||
|
||||
public function assignTaskTo( $task , $user )
|
||||
{
|
||||
$this->query(
|
||||
'UPDATE tasks _task SET user_id_assigned = $2 '
|
||||
. 'FROM tasks _task2 '
|
||||
. 'LEFT OUTER JOIN completed_tasks _completed '
|
||||
. 'USING ( task_id ) '
|
||||
. 'WHERE _task2.task_id = _task.task_id AND _completed.task_id IS NULL AND _task.task_id = $1'
|
||||
)->execute( $task , $user );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,10 +71,16 @@ class Ctrl_TaskDetails
|
|||
if ( $this->task->completed_by === null ) {
|
||||
$box->addButton( BoxButton::create( 'Edit task' , 'tasks/edit?id=' . $this->task->id )
|
||||
->setClass( 'icon edit' ) );
|
||||
|
||||
if ( $tasks->canFinish( $this->task ) ) {
|
||||
$box->addButton( BoxButton::create( 'Mark as completed' , 'tasks/finish?id=' . $this->task->id )
|
||||
->setClass( 'icon stop' ) );
|
||||
};
|
||||
}
|
||||
|
||||
if ( $this->task->assigned_id !== $_SESSION[ 'uid' ] ) {
|
||||
$box->addButton( BoxButton::create( 'Claim task' , 'tasks/claim?id=' . $this->task->id )
|
||||
->setClass( 'icon claim' ) );
|
||||
}
|
||||
} else {
|
||||
if ( $tasks->canRestart( $this->task ) ) {
|
||||
$box->addButton( BoxButton::create( 'Re-activate' , 'tasks/restart?id=' . $this->task->id )
|
||||
|
@ -228,10 +234,12 @@ class Ctrl_EditTask
|
|||
$name = $this->form->field( 'title' );
|
||||
$priority = $this->form->field( 'priority' );
|
||||
$description = $this->form->field( 'description' );
|
||||
$assignee = $this->form->field( 'assigned-to' );
|
||||
|
||||
$error = Loader::DAO( 'tasks' )->updateTask( (int) $id->value( ) ,
|
||||
(int) $item->value( ) , $name->value( ) ,
|
||||
(int) $priority->value( ) , $description->value( ) );
|
||||
(int) $priority->value( ) , $description->value( ) ,
|
||||
(int) $assignee->value( ) );
|
||||
|
||||
switch ( $error ) {
|
||||
|
||||
|
@ -239,11 +247,15 @@ class Ctrl_EditTask
|
|||
return true;
|
||||
|
||||
case 1:
|
||||
$name->putError( "A task already uses this title for this item." );
|
||||
$name->putError( 'A task already uses this title for this item.' );
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$item->putError( "This item has been deleted." );
|
||||
$item->putError( 'This item has been deleted.' );
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$assignee->putError( 'This user has been deleted.' );
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -406,3 +418,23 @@ class Ctrl_DependencyDelete
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Ctrl_TaskClaim
|
||||
extends Controller
|
||||
{
|
||||
|
||||
public function handle( Page $page )
|
||||
{
|
||||
try {
|
||||
$id = (int) $this->getParameter( 'id' );
|
||||
} catch ( ParameterException $e ) {
|
||||
return 'tasks';
|
||||
}
|
||||
|
||||
$dao = Loader::DAO( 'tasks' );
|
||||
$dao->assignTaskTo( $id , $_SESSION[ 'uid' ] );
|
||||
return 'tasks/view?id=' . $id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ $package[ 'ctrls' ][] = 'edit_task_form';
|
|||
$package[ 'ctrls' ][] = 'edit_task';
|
||||
$package[ 'ctrls' ][] = 'task_dependencies';
|
||||
$package[ 'ctrls' ][] = 'task_details';
|
||||
$package[ 'ctrls' ][] = 'task_claim';
|
||||
$package[ 'ctrls' ][] = 'task_notes';
|
||||
$package[ 'ctrls' ][] = 'toggle_task';
|
||||
$package[ 'ctrls' ][] = 'view_task';
|
||||
|
|
|
@ -233,6 +233,9 @@ class Ctrl_EditTaskForm
|
|||
if ( $task === null ) {
|
||||
return 'tasks';
|
||||
}
|
||||
if ( $task->completed_at !== null ) {
|
||||
return 'tasks/view?id=' . $id;
|
||||
}
|
||||
$page->setTitle( $task->title . ' (task)' );
|
||||
|
||||
|
||||
|
@ -253,6 +256,8 @@ class Ctrl_EditTaskForm
|
|||
->setDescription( 'Description:' )
|
||||
->setMandatory( false )
|
||||
->setDefaultValue( $task->description ) )
|
||||
->addField( $this->createAssigneeSelector( )
|
||||
->setDefaultValue( $task->assigned_id ) )
|
||||
->addController( Loader::Ctrl( 'edit_task' ) )
|
||||
->controller( );
|
||||
}
|
||||
|
@ -263,7 +268,7 @@ class Ctrl_EditTaskForm
|
|||
$select = Loader::Create( 'Field' , 'item' , 'select' )
|
||||
->setDescription( 'On item:' );
|
||||
|
||||
$items = Loader::DAO( 'items' )->getTreeList( );
|
||||
$items = Loader::DAO( 'items' )->getTreeList( );
|
||||
foreach ( $items as $item ) {
|
||||
$name = '-' . str_repeat( '--' , $item->depth ) . ' ' . $item->name;
|
||||
$select->addOption( $item->id , $name );
|
||||
|
@ -271,6 +276,22 @@ class Ctrl_EditTaskForm
|
|||
return $select;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function createAssigneeSelector( )
|
||||
{
|
||||
$select = Loader::Create( 'Field' , 'assigned-to' , 'select' )
|
||||
->setDescription( 'Assigned to:' )
|
||||
->setMandatory( false );
|
||||
$select->addOption( '' , '(unassigned task)' );
|
||||
|
||||
$users = Loader::DAO( 'users' )->getUsers( );
|
||||
foreach ( $users as $user ) {
|
||||
$select->addOption( $user->user_id , $user->user_view_name );
|
||||
}
|
||||
|
||||
return $select;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ class Page_TasksTasks
|
|||
parent::__construct( array(
|
||||
'' => 'all_tasks' ,
|
||||
'add' => 'add_task_form' ,
|
||||
'claim' => 'task_claim' ,
|
||||
'delete' => 'delete_task_form' ,
|
||||
'edit' => 'edit_task_form' ,
|
||||
'finish' => array( 'toggle_task' , false ) ,
|
||||
|
|
|
@ -51,20 +51,6 @@ abstract class View_TasksBase
|
|||
return $result;
|
||||
}
|
||||
|
||||
protected abstract function generateItem( $task );
|
||||
}
|
||||
|
||||
|
||||
class View_AllTasks
|
||||
extends View_TasksBase
|
||||
{
|
||||
|
||||
public function __construct( $tasks )
|
||||
{
|
||||
parent::__construct( );
|
||||
$this->tasks = $tasks;
|
||||
}
|
||||
|
||||
protected function generateItem( $task )
|
||||
{
|
||||
$cell = array( );
|
||||
|
@ -72,8 +58,7 @@ class View_AllTasks
|
|||
->appendElement( HTML::make( 'a' )
|
||||
->setAttribute( 'href' , $this->base . '/tasks/view?id=' . $task->id )
|
||||
->appendText( $task->title ) ) );
|
||||
|
||||
array_push( $cell , HTML::make( 'dd' )->append( $this->formatPlaceLineage( $task->item ) ) );
|
||||
$cell = array_merge( $cell , $this->generateSpecificLines( $task ) );
|
||||
|
||||
$addedAt = strtotime( $task->added_at );
|
||||
$addedAtDate = date( 'd/m/o' , $addedAt );
|
||||
|
@ -95,6 +80,11 @@ class View_AllTasks
|
|||
foreach ( $cell as $entry ) {
|
||||
$entry->setAttribute( 'class' , 'missing-deps' );
|
||||
}
|
||||
} elseif ( $task->assigned_to !== null ) {
|
||||
array_push( $cell , HTML::make( 'dd' )->appendText( 'Assigned to ' . $task->assigned_to ) );
|
||||
foreach ( $cell as $entry ) {
|
||||
$entry->setAttribute( 'class' , 'assigned' );
|
||||
}
|
||||
} elseif ( $task->completed_by !== null ) {
|
||||
$completedAt = strtotime( $task->completed_at );
|
||||
$completedAtDate = date( 'd/m/o' , $completedAt );
|
||||
|
@ -110,6 +100,25 @@ class View_AllTasks
|
|||
return $cell;
|
||||
}
|
||||
|
||||
protected abstract function generateSpecificLines( $task );
|
||||
}
|
||||
|
||||
|
||||
class View_AllTasks
|
||||
extends View_TasksBase
|
||||
{
|
||||
|
||||
public function __construct( $tasks )
|
||||
{
|
||||
parent::__construct( );
|
||||
$this->tasks = $tasks;
|
||||
}
|
||||
|
||||
protected function generateSpecificLines( $task )
|
||||
{
|
||||
return array( HTML::make( 'dd' )->append( $this->formatPlaceLineage( $task->item ) ) );
|
||||
}
|
||||
|
||||
private function formatPlaceLineage( $item )
|
||||
{
|
||||
$item = Loader::DAO( 'items' )->get( $item );
|
||||
|
@ -142,47 +151,9 @@ class View_Tasks
|
|||
}
|
||||
|
||||
|
||||
protected function generateItem( $task )
|
||||
protected function generateSpecificLines( $task )
|
||||
{
|
||||
$cell = array( );
|
||||
array_push( $cell , HTML::make( 'dt' )
|
||||
->appendElement( HTML::make( 'a' )
|
||||
->setAttribute( 'href' , $this->base . '/tasks/view?id=' . $task->id )
|
||||
->appendText( $task->title ) ) );
|
||||
|
||||
$addedAt = strtotime( $task->added_at );
|
||||
$addedAtDate = date( 'd/m/o' , $addedAt );
|
||||
$addedAtTime = date( 'H:i:s' , $addedAt );
|
||||
array_push( $cell ,
|
||||
HTML::make( 'dd' )->appendText( "Added $addedAtDate at $addedAtTime by {$task->added_by}" ) );
|
||||
|
||||
if ( $task->missing_dependencies !== null ) {
|
||||
if ( $task->missing_dependencies > 1 ) {
|
||||
$end = 'ies';
|
||||
} else {
|
||||
$end = 'y';
|
||||
}
|
||||
array_push( $cell ,
|
||||
$md = HTML::make( 'dd' )->appendText( "{$task->missing_dependencies} missing dependenc$end" ) );
|
||||
if ( $task->total_missing_dependencies != $task->missing_dependencies ) {
|
||||
$md->appendText( " ({$task->total_missing_dependencies} when counting transitive dependencies)" );
|
||||
}
|
||||
|
||||
foreach ( $cell as $entry ) {
|
||||
$entry->setAttribute( 'class' , 'missing-deps' );
|
||||
}
|
||||
} elseif ( $task->completed_by !== null ) {
|
||||
$completedAt = strtotime( $task->completed_at );
|
||||
$completedAtDate = date( 'd/m/o' , $completedAt );
|
||||
$completedAtTime = date( 'H:i:s' , $completedAt );
|
||||
array_push( $cell , HTML::make( 'dd' )->appendText(
|
||||
"Completed $completedAtDate at $completedAtTime by {$task->completed_by}" ) );
|
||||
foreach ( $cell as $entry ) {
|
||||
$entry->setAttribute( 'class' , 'completed' );
|
||||
}
|
||||
}
|
||||
|
||||
return $cell;
|
||||
return array( );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -225,6 +196,17 @@ class View_TaskDetails
|
|||
->appendElement( HTML::make( 'dd' )
|
||||
->appendText( Loader::DAO( 'tasks' )
|
||||
->translatePriority( $this->task->priority ) ) );
|
||||
|
||||
if ( $this->task->assigned_to === null ) {
|
||||
$list->appendElement( HTML::make( 'dt' )
|
||||
->setAttribute( 'class' , 'unassigned-task' )
|
||||
->appendText( 'Unassigned!' ) );
|
||||
} else {
|
||||
$list->appendElement( HTML::make( 'dt' )
|
||||
->appendText( 'Assigned to:' ) )
|
||||
->appendElement( HTML::make( 'dd' )
|
||||
->appendText( $this->task->assigned_to ) );
|
||||
}
|
||||
} else {
|
||||
$list->appendElement( HTML::make( 'dt' )
|
||||
->appendText( 'Completed:' ) )
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue